App crashes while getting location through ForegroundService - android

I've implemented the exact same example of LocationUpdateForeground Service as provided in these location-samples. The problem is when I run the provided project, it runs but as I'm working with Kotlin and these projects are in Java, my app crashes and givers null pointer exception on mService!!.removeLocationUpdates() but I've tried copying it the exact same without any changes and it still crashes.
I am stuck on getting background location as most of them don't work and this one works exactly even in the Android Q.
The error comes because the onServiceConnected() never gets called in which the mService is being initialized.
Manifest.xml
<service
android:name="com.app.writer.LocationUpdatesService"
android:enabled="true"
android:exported="true"
android:foregroundServiceType="location"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
<intent-filter>
<action android:name="android.accessibilityservice.AccessibilityService" />
</intent-filter>
</service>
MainActivity
class MainActivity : AppCompatActivity(), SharedPreferences.OnSharedPreferenceChangeListener {
var prevMenuItem: MenuItem? = null
private lateinit var tinyDB: TinyDB
private lateinit var myApi: INodeJS
private lateinit var mySnackbar: Snackbar
private var compositeDisposable = CompositeDisposable()
private var mServiceBinder: LocationUpdatesService.LocalBinder? = null
private val TAG = MainActivity::class.java.simpleName
// Used in checking for runtime permissions.
private val REQUEST_PERMISSIONS_REQUEST_CODE = 34
// The BroadcastReceiver used to listen from broadcasts from the service.
private var myReceiver: MainActivity.MyReceiver? =
null
// A reference to the service used to get location updates.
private var mService: LocationUpdatesService? = null
// Tracks the bound state of the service.
private var mBound = false
private val mServiceConnection: ServiceConnection = object :
ServiceConnection {
override fun onServiceConnected(name: ComponentName, service: IBinder) {
mServiceBinder = service as LocationUpdatesService.LocalBinder
mService = mServiceBinder!!.service
mBound = true
}
override fun onServiceDisconnected(name: ComponentName) {
mService = null
mBound = false
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
window.decorView.systemUiVisibility = (
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN)
tinyDB = TinyDB(this) //Easy way to save data in SharedPreferences
myReceiver = MyReceiver()
// Check that the user hasn't revoked permissions by going to Settings.
if (Utils.requestingLocationUpdates(this)) {
if (!checkPermissions()) {
requestPermissions()
}
}
}
override fun onDestroy() {
super.onDestroy()
}
override fun onStop() {
if (mBound) {
// Unbind from the service. This signals to the service that this activity is no longer
// in the foreground, and the service can respond by promoting itself to a foreground
// service.
unbindService(mServiceConnection)
mBound = false
}
PreferenceManager.getDefaultSharedPreferences(this)
.unregisterOnSharedPreferenceChangeListener(this)
super.onStop()
}
override fun onStart() {
super.onStart()
PreferenceManager.getDefaultSharedPreferences(this)
.registerOnSharedPreferenceChangeListener(this)
Writer.setOnClickListener{
if (!checkPermissions()) {
requestPermissions()
} else {
if (tinyDB.getBoolean("Writer")) {
mySnackbar.setText("Kavach Disabled!!!").show()
} else {
mService!!.requestLocationUpdates()
}
}
}
// Restore the state of the buttons when the activity (re)launches.
setButtonsState(Utils.requestingLocationUpdates(this))
// Bind to the service. If the service is in foreground mode, this signals to the service
// that since this activity is in the foreground, the service can exit foreground mode.
applicationContext.bindService(
Intent(this, LocationUpdatesService::class.java), mServiceConnection,
Context.BIND_AUTO_CREATE
)
}
override fun onResume() {
super.onResume()
LocalBroadcastManager.getInstance(this).registerReceiver(
myReceiver!!,
IntentFilter(LocationUpdatesService.ACTION_BROADCAST)
)
}
override fun onPause() {
LocalBroadcastManager.getInstance(this).unregisterReceiver(myReceiver!!)
super.onPause()
}
/**
* Returns the current state of the permissions needed.
*/
private fun checkPermissions(): Boolean {
return PackageManager.PERMISSION_GRANTED == ActivityCompat.checkSelfPermission(
this,
Manifest.permission.ACCESS_FINE_LOCATION
)
}
private fun requestPermissions() {
val shouldProvideRationale =
ActivityCompat.shouldShowRequestPermissionRationale(
this,
Manifest.permission.ACCESS_FINE_LOCATION
)
// Provide an additional rationale to the user. This would happen if the user denied the
// request previously, but didn't check the "Don't ask again" checkbox.
if (shouldProvideRationale) {
Log.i(
TAG,
"Displaying permission rationale to provide additional context."
)
mySnackbar.setText("Location permission is needed for core functionality")
.setAction("Ok", View.OnClickListener { // Request permission
ActivityCompat.requestPermissions(
this#MainActivity,
arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
REQUEST_PERMISSIONS_REQUEST_CODE
)
})
.show()
} else {
Log.i(TAG, "Requesting permission")
// Request permission. It's possible this can be auto answered if device policy
// sets the permission in a given state or the user denied the permission
// previously and checked "Never ask again".
ActivityCompat.requestPermissions(
this#MainActivity,
arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
REQUEST_PERMISSIONS_REQUEST_CODE
)
}
}
/**
* Callback received when a permissions request has been completed.
*/
override fun onRequestPermissionsResult(
requestCode: Int, permissions: Array<String?>,
grantResults: IntArray
) {
Log.i(TAG, "onRequestPermissionResult")
if (requestCode == REQUEST_PERMISSIONS_REQUEST_CODE) {
if (grantResults.isEmpty()) {
// If user interaction was interrupted, the permission request is cancelled and you
// receive empty arrays.
Log.i(TAG, "User interaction was cancelled.")
} else if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// Permission was granted.
mService!!.requestLocationUpdates()
} else {
// Permission denied.
setButtonsState(false)
mySnackbar.setText("Permission Denied, can't work without it")
.setAction(
R.string.settings
) { // Build intent that displays the App settings screen.
val intent = Intent()
intent.action =
android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS
val uri = Uri.fromParts(
"package",
BuildConfig.APPLICATION_ID, null
)
intent.data = uri
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
startActivity(intent)
}
.show()
}
}
}
/**
* Receiver for broadcasts sent by [LocationUpdatesService].
*/
private class MyReceiver : BroadcastReceiver() {
override fun onReceive(
context: Context,
intent: Intent
) {
val location =
intent.getParcelableExtra<Location>(LocationUpdatesService.EXTRA_LOCATION)
if (location != null) {
Toast.makeText(
context, Utils.getLocationText(location),
Toast.LENGTH_SHORT
).show()
}
}
}
override fun onSharedPreferenceChanged(
sharedPreferences: SharedPreferences,
s: String
) {
// Update the buttons state depending on whether location updates are being requested.
if (s == Utils.KEY_REQUESTING_LOCATION_UPDATES) {
setButtonsState(
sharedPreferences.getBoolean(
Utils.KEY_REQUESTING_LOCATION_UPDATES,
false
)
)
}
}
private fun setButtonsState(requestingLocationUpdates: Boolean) {
if (!requestingLocationUpdates) {
mService!!.removeLocationUpdates()
} else {
mService!!.requestLocationUpdates()
}
}
}
I know what a nullpointer exception is, but here I'm stuck and also, it happen because of onStart.

replace your service declaration with
<service android:enabled="true" android:name="com.app.writer.LocationUpdatesService"/>

Related

Location permission dialog not shown in android 12

I'm trying to apply a tutorial about request permission when the app start, but when I tested on my real device android 12, it's not show the permission dialog it show a snack bar that request from user to go to setting and grant it manually
Manifiest file
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- TODO: Step 1 add in permissions for fine location and background-->
<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" />
<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=".HuntMainActivity"
android:label="#string/title_activity_hunt"
android:launchMode="singleInstance"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<receiver android:name=".GeofenceBroadcastReceiver"/>
</application>
</manifest>
this my code
class HuntMainActivity : AppCompatActivity() {
private lateinit var binding: ActivityHuntMainBinding
private lateinit var geofencingClient: GeofencingClient
private lateinit var viewModel: GeofenceViewModel
private val runningQOrLater = android.os.Build.VERSION.SDK_INT >=
android.os.Build.VERSION_CODES.Q
// A PendingIntent for the Broadcast Receiver that handles geofence transitions.
// TODO: Step 8 add in a pending intent
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_hunt_main)
viewModel = ViewModelProvider(this, SavedStateViewModelFactory(this.application,
this)).get(GeofenceViewModel::class.java)
binding.viewmodel = viewModel
binding.lifecycleOwner = this
// TODO: Step 9 instantiate the geofencing client
// Create channel for notifications
createChannel(this )
}
override fun onStart() {
super.onStart()
checkPermissionsAndStartGeofencing()
}
/*
* When we get the result from asking the user to turn on device location, we call
* checkDeviceLocationSettingsAndStartGeofence again to make sure it's actually on, but
* we don't resolve the check to keep the user from seeing an endless loop.
*/
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
// TODO: Step 7 add code to check that the user turned on their device location and ask
// again if they did not
}
/*
* When the user clicks on the notification, this method will be called, letting us know that
* the geofence has been triggered, and it's time to move to the next one in the treasure
* hunt.
*/
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
val extras = intent?.extras
if(extras != null){
if(extras.containsKey(GeofencingConstants.EXTRA_GEOFENCE_INDEX)){
viewModel.updateHint(extras.getInt(GeofencingConstants.EXTRA_GEOFENCE_INDEX))
checkPermissionsAndStartGeofencing()
}
}
}
/*
* In all cases, we need to have the location permission. On Android 10+ (Q) we need to have
* the background permission as well.
*/
#SuppressLint("MissingSuperCall")
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<String>,
grantResults: IntArray
) {
Log.d(TAG, "onRequestPermissionResult")
if (
grantResults.isEmpty() ||
grantResults[LOCATION_PERMISSION_INDEX] == PackageManager.PERMISSION_DENIED ||
(requestCode == REQUEST_FOREGROUND_AND_BACKGROUND_PERMISSION_RESULT_CODE &&
grantResults[BACKGROUND_LOCATION_PERMISSION_INDEX] ==
PackageManager.PERMISSION_DENIED))
{
Snackbar.make(
binding.activityMapsMain,
R.string.permission_denied_explanation,
Snackbar.LENGTH_INDEFINITE
)
.setAction(R.string.settings) {
startActivity(Intent().apply {
action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS
data = Uri.fromParts("package", BuildConfig.APPLICATION_ID, null)
flags = Intent.FLAG_ACTIVITY_NEW_TASK
})
}.show()
} else {
checkDeviceLocationSettingsAndStartGeofence()
}
}
/**
* This will also destroy any saved state in the associated ViewModel, so we remove the
* geofences here.
*/
override fun onDestroy() {
super.onDestroy()
removeGeofences()
}
/**
* Starts the permission check and Geofence process only if the Geofence associated with the
* current hint isn't yet active.
*/
private fun checkPermissionsAndStartGeofencing() {
if (viewModel.geofenceIsActive()) return
if (foregroundAndBackgroundLocationPermissionApproved()) {
checkDeviceLocationSettingsAndStartGeofence()
} else {
requestForegroundAndBackgroundLocationPermissions()
}
}
/*
* Uses the Location Client to check the current state of location settings, and gives the user
* the opportunity to turn on location services within our app.
*/
private fun checkDeviceLocationSettingsAndStartGeofence(resolve:Boolean = true) {
val locationRequest = LocationRequest.create().apply {
priority = LocationRequest.PRIORITY_LOW_POWER
}
val builder = LocationSettingsRequest.Builder().addLocationRequest(locationRequest)
val settingsClient = LocationServices.getSettingsClient(this)
val locationSettingsResponseTask =
settingsClient.checkLocationSettings(builder.build())
locationSettingsResponseTask.addOnFailureListener { exception ->
if (exception is ResolvableApiException && resolve){
try {
exception.startResolutionForResult(this#HuntMainActivity,
REQUEST_TURN_DEVICE_LOCATION_ON)
} catch (sendEx: IntentSender.SendIntentException) {
Log.d(TAG, "Error getting location settings resolution: " + sendEx.message)
}
} else {
Snackbar.make(
binding.activityMapsMain,
R.string.location_required_error, Snackbar.LENGTH_INDEFINITE
).setAction(android.R.string.ok) {
checkDeviceLocationSettingsAndStartGeofence()
}.show()
}
}
locationSettingsResponseTask.addOnCompleteListener {
if ( it.isSuccessful ) {
addGeofenceForClue()
}
}
}
/*
* Determines whether the app has the appropriate permissions across Android 10+ and all other
* Android versions.
*/
#TargetApi(29)
private fun foregroundAndBackgroundLocationPermissionApproved(): Boolean {
val foregroundLocationApproved = (
PackageManager.PERMISSION_GRANTED ==
ActivityCompat.checkSelfPermission(this,
Manifest.permission.ACCESS_FINE_LOCATION))
val backgroundPermissionApproved =
if (runningQOrLater) {
PackageManager.PERMISSION_GRANTED ==
ActivityCompat.checkSelfPermission(
this, Manifest.permission.ACCESS_BACKGROUND_LOCATION
)
} else {
true
}
return foregroundLocationApproved && backgroundPermissionApproved
}
/*
* Requests ACCESS_FINE_LOCATION and (on Android 10+ (Q) ACCESS_BACKGROUND_LOCATION.
*/
#TargetApi(29 )
private fun requestForegroundAndBackgroundLocationPermissions() {
if (foregroundAndBackgroundLocationPermissionApproved())
return
var permissionsArray = arrayOf(Manifest.permission.ACCESS_FINE_LOCATION)
val resultCode = when {
runningQOrLater -> {
permissionsArray += Manifest.permission.ACCESS_BACKGROUND_LOCATION
REQUEST_FOREGROUND_AND_BACKGROUND_PERMISSION_RESULT_CODE
}
else -> REQUEST_FOREGROUND_ONLY_PERMISSIONS_REQUEST_CODE
}
Log.d(TAG, "Request foreground only location permission")
ActivityCompat.requestPermissions(
this#HuntMainActivity,
permissionsArray,
resultCode
)
}
/*
* Adds a Geofence for the current clue if needed, and removes any existing Geofence. This
* method should be called after the user has granted the location permission. If there are
* no more geofences, we remove the geofence and let the viewmodel know that the ending hint
* is now "active."
*/
private fun addGeofenceForClue() {
// TODO: Step 10 add in code to add the geofence
}
/**
* Removes geofences. This method should be called after the user has granted the location
* permission.
*/
private fun removeGeofences() {
// TODO: Step 12 add in code to remove the geofences
}
companion object {
internal const val ACTION_GEOFENCE_EVENT =
"HuntMainActivity.treasureHunt.action.ACTION_GEOFENCE_EVENT"
}
}
private const val REQUEST_FOREGROUND_AND_BACKGROUND_PERMISSION_RESULT_CODE = 33
private const val REQUEST_FOREGROUND_ONLY_PERMISSIONS_REQUEST_CODE = 34
private const val REQUEST_TURN_DEVICE_LOCATION_ON = 29
private const val TAG = "HuntMainActivity"
private const val LOCATION_PERMISSION_INDEX = 0
private const val BACKGROUND_LOCATION_PERMISSION_INDEX = 1
I tested it on my real device, what i excopecting is showing the permission dialog when the app starts

onRequestPermissionsResult Deprecated [duplicate]

I tried to implement request permissions for writing and reading from storage. Everything worked good but today Android showed me that the method onRequestPermissionsResult(...) is deprecated. There are so many questions about this topic in StackOverflow, but unfortunately, they are outdated.
I called the methods below in a fragment.
It was suggested simply to call:
requestPermissions(new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE},
StorageKeys.STORAGE_PERMISSION_CODE)
instead of my approach:
ActivityCompat.requestPermissions(getActivity(),
new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE},
StorageKeys.STORAGE_PERMISSION_CODE))
But both of them show that onRequestPermissionsResult(...) is deprecated.
Here is my onRequestPermissionsResult(...)-method:
#Override
public void onRequestPermissionsResult(int requestCode, #NonNull String[] permissions,
#NonNull int[] grantResults) {
if (requestCode == StorageKeys.STORAGE_PERMISSION_CODE) {
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
exportBibTex.createBibFile();
exportBibTex.writeBibFile(exportBibTex
.getBibDataLibrary(libraryModel, bookDao, noteDao));
Toast.makeText(getContext(),
getString(R.string.exported_file_stored_in) + '\n'
+ File.separator + StorageKeys.DOWNLOAD_FOLDER + File.separator + fileName
+ StorageKeys.BIB_FILE_TYPE, Toast.LENGTH_LONG).show();
} else {
Toast.makeText(getContext(), R.string.storage_permission_denied,
Toast.LENGTH_SHORT).show();
}
}
}
Here is a simple alert dialog, in which I call the onRequestPermissionsResult(...):
private void showRequestPermissionDialog() {
AlertDialog.Builder reqAlertDialog = new AlertDialog.Builder(getContext());
reqAlertDialog.setTitle(R.string.storage_permission_needed);
reqAlertDialog.setMessage(R.string.storage_permission_alert_msg);
reqAlertDialog.setPositiveButton(R.string.ok,
(dialog, which) -> ActivityCompat.requestPermissions(getActivity(),
new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE},
StorageKeys.STORAGE_PERMISSION_CODE));
reqAlertDialog.setNegativeButton(R.string.cancel,
(dialog, which) -> dialog.dismiss());
reqAlertDialog.create().show();
}
Is there any alternative for onRequestPermissionsResult(...), that I can use?
The onRequestPermissionsResult() method is deprecated in androidx.fragment.app.Fragment.
So you may use registerForActivityResult() method instead of onRequestPermissionsResult().
You can refer this URL.
Following is Kotlin code, but you can refer it:
val requestPermissionLauncher = registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted ->
if (isGranted) {
// PERMISSION GRANTED
} else {
// PERMISSION NOT GRANTED
}
}
// Ex. Launching ACCESS_FINE_LOCATION permission.
private fun startLocationPermissionRequest() {
requestPermissionLauncher.launch(Manifest.permission.ACCESS_FINE_LOCATION)
}
I added java code from following URL.
How to get a permission request in new ActivityResult API (1.3.0-alpha05)?
private ActivityResultLauncher<String> requestPermissionLauncher = registerForActivityResult(
new ActivityResultContracts.RequestPermission(),
new ActivityResultCallback<Boolean>() {
#Override
public void onActivityResult(Boolean result) {
if (result) {
// PERMISSION GRANTED
} else {
// PERMISSION NOT GRANTED
}
}
}
);
// Ex. Launch the permission window -- this is in onCreateView()
floatingActionButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
requestPermissionLauncher.launch(Manifest.permission.ACCESS_FINE_LOCATION);
}
});
You can also request multiple permissions:
val requestMultiplePermissions = registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) { permissions ->
permissions.entries.forEach {
Log.d("DEBUG", "${it.key} = ${it.value}")
}
}
requestMultiplePermissions.launch(
arrayOf(
Manifest.permission.READ_CONTACTS,
Manifest.permission.ACCESS_FINE_LOCATION
)
)
A simple way in Kotlin
import android.Manifest
import android.content.Context
import android.content.pm.PackageManager
import android.os.Build
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.app.ActivityCompat
import androidx.fragment.app.Fragment
class MyFragment : Fragment() {
companion object {
val TAG: String = MyFragment::class.java.simpleName
var PERMISSIONS = arrayOf(
Manifest.permission.CAMERA,
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE
)
}
private val permReqLauncher =
registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions ->
val granted = permissions.entries.all {
it.value == true
}
if (granted) {
displayCameraFragment()
}
}
private fun takePhoto() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
displayCameraFragment()
return
}
activity?.let {
if (hasPermissions(activity as Context, PERMISSIONS)) {
displayCameraFragment()
} else {
permReqLauncher.launch(
PERMISSIONS
)
}
}
}
// util method
private fun hasPermissions(context: Context, permissions: Array<String>): Boolean = permissions.all {
ActivityCompat.checkSelfPermission(context, it) == PackageManager.PERMISSION_GRANTED
}
private fun displayCameraFragment() {
// open camera fragment
}
}
Alternative registerForActivityResult
Example use:
private fun permissionSetup() {
val permission = ContextCompat.checkSelfPermission(
requireContext(), Manifest.permission.READ_CONTACTS)
if (permission != PackageManager.PERMISSION_GRANTED) {
permissionsResultCallback.launch(Manifest.permission.READ_CONTACTS)
} else {
println("Permission isGranted")
}
}
private val permissionsResultCallback = registerForActivityResult(
ActivityResultContracts.RequestPermission()){
when (it) {
true -> { println("Permission has been granted by user") }
false -> {
Toast.makeText(requireContext(), "Permission denied", Toast.LENGTH_SHORT).show()
dialog.dismiss()
}
}
}
Register the permissions callback in your activity or fragment. which handle user permission
Example-Storage permission
private final ActivityResultLauncher<String> requestPermissionLauncher = registerForActivityResult(
new ActivityResultContracts.RequestPermission(),
result -> {
if (result) {
//Permission granted
} else {
//permission denied
if (ActivityCompat.shouldShowRequestPermissionRationale(activity, WRITE_EXTERNAL_STORAGE)) {
//show permission snackbar
} else {
//display error dialog
}
}
});
Ask for the permission.The registered ActivityResultCallback gets the result of this request.
if (checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PermissionChecker.PERMISSION_GRANTED) {
requestPermissionLauncher.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE);
} else {
//Permission already granted
}
This works for me - (kotlin):
private fun checkPermissions() {
if (mContext?.let {
ContextCompat.checkSelfPermission(
it,
READ_EXTERNAL_STORAGE
)
} != PackageManager.PERMISSION_GRANTED) {
Log.d(TAG, "Request Permissions")
requestMultiplePermissions.launch(
arrayOf(READ_EXTERNAL_STORAGE, WRITE_EXTERNAL_STORAGE))
} else {
Log.d(TAG, "Permission Already Granted")
}
}
private val requestMultiplePermissions =
registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions ->
permissions.entries.forEach {
Log.d(TAG, "${it.key} = ${it.value}")
}
if (permissions[READ_EXTERNAL_STORAGE] == true && permissions[WRITE_EXTERNAL_STORAGE] == true) {
Log.d(TAG, "Permission granted")
} else {
Log.d(TAG, "Permission not granted")
}
}
Most of the answers address the OP requirement. But I have found few things that are missing so I thought to provide a complete example (in Koltin)
class ProfileFragment : Fragment(){
private lateinit var permissionRequest : ActivityResultLauncher<Array<String>>
companion object {
val LOCATION_PERMISSIONS = arrayOf(
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION
)
}
private fun getGpsLocation() {
if(activity != null){
permissionRequest.launch(LOCATION_PERMISSIONS)
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.locBtn.setOnClickListener { getGpsLocation() }
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
registerPermissionRequest()
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
binding = DataBindingUtil.inflate(inflater, R.layout.fragment_profile, container, false)
return binding.root
}
private fun registerPermissionRequest(){
var permissionCount = 0
permissionRequest = registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions ->
permissions.entries.forEach {
if(it.value){
permissionCount++
}
}
if(permissionCount == 2){
getMyLocation()
}
}
}
}
The things that are missing are
A)Fragments must call registerForActivityResult() before they are created (i.e. initialization, onAttach(), or onCreate()). Other wise it won't work and the app would crash.
Error:
java.lang.IllegalStateException: Fragment ProfileFragment{bf12414} (210ad5a1-3286-4586-a48f-deac1d8e3eef
id=0x7f09008b) is attempting to registerForActivityResult after being
created. Fragments must call registerForActivityResult() before they
are created (i.e. initialization, onAttach(), or onCreate()).
B) It is recommended to request permission when it is really needed. In my example when user clicks on Button with id locBtn, permission dialog is shown rather than showing when activity/fragment is created.
Please refer to the official documentation: https://developer.android.com/training/permissions/requesting
Review the following, which can be found in the documentation.
// Register the permissions callback, which handles the user's response to the
// system permissions dialog. Save the return value, an instance of
// ActivityResultLauncher. You can use either a val, as shown in this snippet,
// or a lateinit var in your onAttach() or onCreate() method.
val requestPermissionLauncher =
registerForActivityResult(RequestPermission()
) { isGranted: Boolean ->
if (isGranted) {
// Permission is granted. Continue the action or workflow in your
// app.
} else {
// Explain to the user that the feature is unavailable because the
// features requires a permission that the user has denied. At the
// same time, respect the user's decision. Don't link to system
// settings in an effort to convince the user to change their
// decision.
}
}
After which, launch the request
when {
ContextCompat.checkSelfPermission(
CONTEXT,
Manifest.permission.REQUESTED_PERMISSION
) == PackageManager.PERMISSION_GRANTED -> {
// You can use the API that requires the permission.
}
//Is not needed for it to work, but is a good practice as it plays a role
//in letting user know why the permission is needed.
shouldShowRequestPermissionRationale(...) -> {
// In an educational UI, explain to the user why your app requires this
// permission for a specific feature to behave as expected. In this UI,
// include a "cancel" or "no thanks" button that allows the user to
// continue using your app without granting the permission.
showInContextUI(...)
}
else -> {
// You can directly ask for the permission.
// The registered ActivityResultCallback gets the result of this request.
requestPermissionLauncher.launch(
Manifest.permission.REQUESTED_PERMISSION)
}
}
Suppose we need to take some permissions like audio and camera. Then we can put it on a variable of array.
private val REQUESTED_PERMISSIONS =
arrayOf(Manifest.permission.RECORD_AUDIO, Manifest.permission.CAMERA)
This registerForActivityResult invoke when we launch with multiple permission array.
private val requestMultiplePermissions = registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) { permissions ->
permissions.entries.forEach {
Log.d("DEBUG", "${it.key} = ${it.value}")
if (!it.value){
return#registerForActivityResult
}
}
init()
}
Now we make a function to check permission is already granted or not.
private fun checkSelfPermission(): Boolean {
return !(ContextCompat.checkSelfPermission(
requireContext(),
REQUESTED_PERMISSIONS[0]
) != PackageManager.PERMISSION_GRANTED ||
ContextCompat.checkSelfPermission(
requireContext(),
REQUESTED_PERMISSIONS[1]
) != PackageManager.PERMISSION_GRANTED)
}
Now we check permission is granted or not from onCreate method. If permission is not granted then we invoke requestMultiplePermissions.launch(REQUESTED_PERMISSIONS) like
super.onCreate(savedInstanceState)
if (!checkSelfPermission()) {
requestMultiplePermissions.launch(REQUESTED_PERMISSIONS)
}
}
You can use some external library for permission handling to reduce some boilerplate code. I use Dexter library. Rx Permissions is also good choice if you are using RxJava2.
Try this in in your pubspec.yamal under flutter section:
plugin:
androidPackage: com.ly.permission
pluginClass: PermissionPlugin
It works with me.

Small Kotlin demo ACCESS_FINE_LOCATION

Improved version!
The following code shows how to ACCESS_FINE_LOCATION. While longer than my original code it is preferred because it stops GPS updates when the App is Paused or Stopped.
Due to issues with the Android Studio emulators it also includes a simple watchdog timer reporting the GPS Updates have stopped if no updates are received for approximately 10 seconds
const val PERMISSIONS_REQUEST_ACCESS_LOCATION = 99 // any positive value
class MainActivity : AppCompatActivity() {
private lateinit var tvCurrentTime : TextView
private lateinit var tvSpeed : TextView
private lateinit var tvBearing : TextView
private var myTimer: Timer = Timer()
private var sHourMinuteSecond : String = "00:00:00"
private lateinit var fusedLocationClient : FusedLocationProviderClient
private lateinit var locationCallback: LocationCallback
private var gpsWatchDog : Int = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
tvCurrentTime = findViewById<View>(R.id.show_time_textview) as TextView
tvSpeed = findViewById<View>(R.id.speed_textview) as TextView
tvBearing = findViewById<View>(R.id.bearing_textview) as TextView
fusedLocationClient = LocationServices.getFusedLocationProviderClient(this#MainActivity) as FusedLocationProviderClient
locationCallback = object : LocationCallback() {
override fun onLocationResult(locationResult: LocationResult) {
super.onLocationResult(locationResult)
processNewLocation(locationResult.lastLocation) // only process the lastLocation (may be others)
}
}
}
private fun checkPermissionsForGPSUpdates() {
if ((ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) ||
(ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED)) {
// Android wants us to request both COARSE and FINE permissions
writeToLog("checkSelfPermission Permission Not Granted so requesting!")
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION), PERMISSIONS_REQUEST_ACCESS_LOCATION)
// During requestPermission OnPause() is called
// When user grants permission OnResume() is called which once again calls checkPermissionsForGPSUpdates() when GPS updates should commence
return
}
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
// only run if we have FINE location
writeToLog("ACCESS_FINE_LOCATION Permission Granted start GPS updates !")
val locationRequest: LocationRequest = LocationRequest.create().apply {
interval = 1000
fastestInterval = 500
priority = LocationRequest.PRIORITY_HIGH_ACCURACY
}
try {
if (!fusedLocationClient.requestLocationUpdates(locationRequest, locationCallback, Looper.getMainLooper()).isSuccessful) {
writeToLog("requestLocationUpdates() was unsuccessful") // Stackoverflow indicates this may actually be a google error
}
} catch (unlikely: SecurityException) {
writeToLog("Lost location permission. Could not request updates. $unlikely")
}
} else {
writeToLog("Permissions Not Granted so exit")
Toast.makeText(this#MainActivity, "App will not run without GPS" , Toast.LENGTH_LONG).show()
finish()
}
}
private fun updateTimeString() {
// Called by the timer DO NOT put code affecting the UI in here!!!
val systemTime = GregorianCalendar()
sHourMinuteSecond = SimpleDateFormat("HH:mm:ss").format(systemTime.timeInMillis)
// Use the runOnUiThread method to update the display
this.runOnUiThread(updateDisplay)
}
private val updateDisplay = Runnable {
// This method runs in the same thread as the User Interface
// due to continuous crashing of the emulator has been written to be short
tvCurrentTime.text = sHourMinuteSecond
if (++gpsWatchDog > 10) {
tvBearing.text = "No GPS"
}
}
private fun processNewLocation(latestLocation : Location) {
// reset the watchdog since we have received a GPS update
gpsWatchDog = 0
// do something with the latest location
// for now just show the GPS time
val sSpeed = SimpleDateFormat("HH:mm:ss").format(latestLocation.time)
tvSpeed.text = sSpeed
val sBearing = latestLocation.bearing.roundToInt().toString() // no decimal places
tvBearing.text = sBearing
}
override fun onResume() {
super.onResume()
writeToLog("Called onResume()")
// start the GPS updates (after checking user permission)
checkPermissionsForGPSUpdates()
val currentSystemTime = System.currentTimeMillis()
// start a timer to update the time
myTimer = Timer()
val delayToNextWholeSecond = ((currentSystemTime / 1000) + 1) * 1000 - currentSystemTime // Synchronise to whole seconds
myTimer.scheduleAtFixedRate(object : TimerTask() {
override fun run() {
updateTimeString()
}
}, delayToNextWholeSecond, 1000)
}
override fun onPause() {
super.onPause()
writeToLog("onPause()")
myTimer.cancel() // stop the existing timer
myTimer.purge()
fusedLocationClient.removeLocationUpdates(locationCallback)
}
override fun onStop() {
super.onStop()
writeToLog("onStop()")
myTimer.cancel()
myTimer.purge()
fusedLocationClient.removeLocationUpdates(locationCallback)
}
private fun writeToLog(message : String) {
Log.d("SmallGPSDemo", message)
}
}
The original very short example is shown below only to show Android's new RequestPermission() to obtain permissions (and start updates)
class MainActivity : AppCompatActivity() {
private var debugTextView : TextView? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
debugTextView = findViewById<View>(R.id.debug_textview) as TextView
// this checks permission and starts the location updates
requestPermission.launch(Manifest.permission.ACCESS_FINE_LOCATION)
}
private fun processNewLocation(latestLocation : Location) {
// do something with the location
debugTextView!!.text = SimpleDateFormat("hh:mm:ss.SSS").format(System.currentTimeMillis())
}
#SuppressLint("MissingPermission")
private val requestPermission =
registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
if (isGranted) {
// Permissions granted so start GPS updates
val locationRequest: LocationRequest = LocationRequest.create().apply {
interval = 1000
fastestInterval = 500
priority = LocationRequest.PRIORITY_HIGH_ACCURACY
}
val locationCallback = object : LocationCallback() {
override fun onLocationResult(locationResult: LocationResult) {
super.onLocationResult(locationResult)
for (location in locationResult.locations) {
processNewLocation(location) // Settings should only give one location but ...
}
}
}
val fusedLocationClient = LocationServices.getFusedLocationProviderClient(this#MainActivity)
fusedLocationClient.requestLocationUpdates(locationRequest, locationCallback, Looper.getMainLooper())
} else {
Log.d("DEBUG", "permission denied ")
Toast.makeText(this#MainActivity, "This activity will not run unless you allow GPS", Toast.LENGTH_LONG).show()
finish()
}
}
}
Reminder add location permissions to the AndroidManifest file
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.reallysmallgpscode">
<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/Theme.ReallySmallGPSCode">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
Hopefully others find the code useful

Android ForegroundService for background location

From Android 11 I learned there are some restrictions related to background location, but from the documentation is not very clear to me if this affects a ForegroundService which has the foregroundServiceType="location" declared in the AndroidManifest.xml file.
This part of the documentation is confusing for me:
"If your app starts a foreground service while the app is running in
the foreground ("while-in-use"), the service has the following access
restrictions:
If the user has granted the ACCESS_BACKGROUND_LOCATION permission to
your app, the service can access location all the time. Otherwise, if
the user has granted the ACCESS_FINE_LOCATION or
ACCESS_COARSE_LOCATION permission to your app, the service has access
to location only while the app is running in the foreground (also
known as "while-in-use access to location")."
So, if I need background location access is it safe to use only the ForegroundService with type "location" for Android 11 or it is still mandatory to add the ACCESS_BACKGROUND_LOCATION permission?
NOTE: I created a sample project with ForegroundService declared with type "location" for target SDK 30 and seems to work without the background location permission (I receive the location updates every 2 seconds while in background) and this is why I am confused about this. I run the app on Pixel 4 with Android 11.
This is the sample project:
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.locationforegroundservice">
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<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.LocationForegroundService">
<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=".LocationService"
android:enabled="true"
android:exported="true"
android:foregroundServiceType="location"/>
</application>
</manifest>
LocationService
class LocationService : Service() {
private var context: Context? = null
private var settingsClient: SettingsClient? = null
private var locationSettingsRequest: LocationSettingsRequest? = null
private var locationManager: LocationManager? = null
private var locationRequest: LocationRequest? = null
private var notificationManager: NotificationManager? = null
private var fusedLocationClient: FusedLocationProviderClient? = null
private val binder: IBinder = LocalBinder()
private var locationCallback: LocationCallback? = null
private var location: Location? = null
override fun onBind(intent: Intent?): IBinder {
// Called when a client (MainActivity in case of this sample) comes to the foreground
// and binds with this service. The service should cease to be a foreground service
// when that happens.
Log.i(TAG, "in onBind()")
return binder
}
override fun onCreate() {
super.onCreate()
context = this
fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
createLocationRequest()
locationCallback = object : LocationCallback() {
#RequiresApi(Build.VERSION_CODES.O)
override fun onLocationResult(locationResult: LocationResult) {
super.onLocationResult(locationResult)
for (location in locationResult.locations) {
onNewLocation(location)
}
}
}
val handlerThread = HandlerThread(TAG)
handlerThread.start()
notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager?
// Android O requires a Notification Channel.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val name: CharSequence = "service"
val mChannel = NotificationChannel(CHANNEL_ID, name, NotificationManager.IMPORTANCE_DEFAULT)
// Set the Notification Channel for the Notification Manager.
notificationManager?.createNotificationChannel(mChannel)
}
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Log.i(TAG, "Service started")
val startedFromNotification =
intent?.getBooleanExtra(EXTRA_STARTED_FROM_NOTIFICATION, false)
// We got here because the user decided to remove location updates from the notification.
if (startedFromNotification == true) {
removeLocationUpdates()
stopSelf()
}
// Tells the system to not try to recreate the service after it has been killed.
return START_NOT_STICKY
}
/**
* Returns the [NotificationCompat] used as part of the foreground service.
*/
private val notification: Notification
private get() {
val intent = Intent(this, LocationService::class.java)
// Extra to help us figure out if we arrived in onStartCommand via the notification or not.
intent.putExtra(EXTRA_STARTED_FROM_NOTIFICATION, true)
// The PendingIntent that leads to a call to onStartCommand() in this service.
val servicePendingIntent =
PendingIntent.getService(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
// The PendingIntent to launch activity.
val activityPendingIntent =
PendingIntent.getActivity(this, 0, Intent(this, MainActivity::class.java), 0)
val builder = NotificationCompat.Builder(this)
.addAction(R.drawable.ic_delete, "title", activityPendingIntent)
.addAction(R.drawable.ic_delete, "remove", servicePendingIntent)
.setContentTitle("location title").setOngoing(true)
.setPriority(Notification.PRIORITY_HIGH).setSmallIcon(R.drawable.btn_dialog)
.setWhen(System.currentTimeMillis())
// Set the Channel ID for Android O.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
builder.setChannelId(CHANNEL_ID) // Channel ID
}
return builder.build()
}
/**
* Makes a request for location updates. Note that in this sample we merely log the
* [SecurityException].
*/
fun requestLocationUpdates() {
Log.i(TAG, "Requesting location updates")
startForeground(NOTIFICATION_ID, notification)
try {
fusedLocationClient?.requestLocationUpdates(locationRequest, locationCallback, null)
} catch (unlikely: SecurityException) {
Log.e(TAG, "Lost location permission. Could not request updates. $unlikely")
}
}
#RequiresApi(Build.VERSION_CODES.O)
private fun onNewLocation(location: Location) {
Log.i(TAG, "New location ${LocalDateTime.now()}: $location")
this.location = location
// Notify anyone listening for broadcasts about the new location.
val intent = Intent(ACTION_BROADCAST)
intent.putExtra(EXTRA_LOCATION, location)
LocalBroadcastManager.getInstance(applicationContext).sendBroadcast(intent)
// Update notification content if running as a foreground service.
if (serviceIsRunningInForeground(this)) {
notificationManager?.notify(NOTIFICATION_ID, notification)
}
}
/**
* Sets the location request parameters.
*/
private fun createLocationRequest() {
locationManager = context?.getSystemService(LOCATION_SERVICE) as LocationManager
settingsClient = LocationServices.getSettingsClient(context)
locationRequest = LocationRequest.create()
locationRequest?.priority = LocationRequest.PRIORITY_HIGH_ACCURACY
locationRequest?.interval = 1000
locationRequest?.fastestInterval = 1000
val builder = LocationSettingsRequest.Builder().addLocationRequest(locationRequest)
locationSettingsRequest = builder.build()
builder.setAlwaysShow(true) //this is the key ingredient
}
/**
* Removes location updates. Note that in this sample we merely log the
* [SecurityException].
*/
fun removeLocationUpdates() {
Log.i(TAG, "Removing location updates")
try {
fusedLocationClient?.removeLocationUpdates(locationCallback)
stopSelf()
} catch (unlikely: SecurityException) {
Log.e(TAG, "Lost location permission. Could not remove updates. $unlikely")
}
}
/**
* Class used for the client Binder. Since this service runs in the same process as its
* clients, we don't need to deal with IPC.
*/
inner class LocalBinder : Binder() {
val service: LocationService
get() = this#LocationService
}
/**
* Returns true if this is a foreground service.
*
* #param context The [Context].
*/
fun serviceIsRunningInForeground(context: Context): Boolean {
val manager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
for (service in manager.getRunningServices(Int.MAX_VALUE)) {
if (javaClass.name == service.service.className) {
if (service.foreground) {
return true
}
}
}
return false
}
companion object {
private const val PACKAGE_NAME = "com.example.locationforegroundservice"
private val TAG = "TEST"
/**
* The name of the channel for notifications.
*/
private const val CHANNEL_ID = "channel_01"
const val ACTION_BROADCAST = PACKAGE_NAME + ".broadcast"
const val EXTRA_LOCATION = PACKAGE_NAME + ".location"
private const val EXTRA_STARTED_FROM_NOTIFICATION =
PACKAGE_NAME + ".started_from_notification"
/**
* The desired interval for location updates. Inexact. Updates may be more or less frequent.
*/
private const val UPDATE_INTERVAL_IN_MILLISECONDS: Long = 1000
/**
* The fastest rate for active location updates. Updates will never be more frequent
* than this value.
*/
private const val FASTEST_UPDATE_INTERVAL_IN_MILLISECONDS =
UPDATE_INTERVAL_IN_MILLISECONDS / 2
/**
* The identifier for the notification displayed for the foreground service.
*/
private const val NOTIFICATION_ID = 12345678
}
MainActivity
class MainActivity : AppCompatActivity() {
private val TAG = "TEST"
private val FOREGROUND_LOCATION_CODE = 2
// The BroadcastReceiver used to listen from broadcasts from the service.
private var myReceiver: MyReceiver? = null
// A reference to the service used to get location updates.
private var mService: LocationService? = null
// Monitors the state of the connection to the service.
private val mServiceConnection: ServiceConnection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName, service: IBinder) {
val binder: LocationService.LocalBinder = service as LocationService.LocalBinder
mService = binder.service
}
override fun onServiceDisconnected(name: ComponentName) {
mService = null
}
}
#RequiresApi(Build.VERSION_CODES.M)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
checkForegroundLocationPermission()
myReceiver = MyReceiver()
myReceiver?.let {
LocalBroadcastManager.getInstance(this)
.registerReceiver(it, IntentFilter(LocationService.ACTION_BROADCAST))
}
findViewById<Button>(R.id.start).setOnClickListener { view ->
Snackbar.make(view, "Start listening...", Snackbar.LENGTH_LONG).show()
Log.d("TEST", "Start listening...")
mService?.requestLocationUpdates();
}
findViewById<Button>(R.id.stop).setOnClickListener { view ->
Snackbar.make(view, "Stop listening...", Snackbar.LENGTH_LONG).show()
Log.d("TEST", "Stop listening...")
mService?.removeLocationUpdates()
}
}
override fun onStart() {
super.onStart()
// Bind to the service. If the service is in foreground mode, this signals to the service
// that since this activity is in the foreground, the service can exit foreground mode.
// Bind to the service. If the service is in foreground mode, this signals to the service
// that since this activity is in the foreground, the service can exit foreground mode.
Intent(this, LocationService::class.java).also {
bindService(it, mServiceConnection, BIND_AUTO_CREATE)
}
}
override fun onResume() {
super.onResume()
Log.d(TAG, "onResume")
}
override fun onStop() {
Log.d(TAG, "onStop")
super.onStop()
}
#RequiresApi(Build.VERSION_CODES.M)
private fun checkForegroundLocationPermission() {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
// Check if permission is not granted
Log.d(TAG, "Permission for foreground location is not granted")
requestPermissions(arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
FOREGROUND_LOCATION_CODE)
} else {
// Permission is already granted, do your magic here!
Toast.makeText(this, "Permission granted", Toast.LENGTH_SHORT).show()
}
}
#RequiresApi(Build.VERSION_CODES.Q)
override fun onRequestPermissionsResult(requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray) {
when (requestCode) {
FOREGROUND_LOCATION_CODE -> {
Log.d(TAG, "onRequestPermissionsResult -> FOREGROUND_LOCATION_CODE")
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Toast.makeText(this, "Foreground Permission granted", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(this, "Foreground Permission denied", Toast.LENGTH_SHORT).show()
}
return
}
}
}
private class MyReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val location: Location? = intent.getParcelableExtra(LocationService.EXTRA_LOCATION)
if (location != null) {
Log.d("TEST", "Location = $location")
}
}
}
}

Detect NFC tag through all the activities

I'm working on Android NFC based application with requirement of continuously read/write data to SLIX-2(ICode) tag from any activity.
As of now, application starts to initialize NFCManager which does most of the heavy lifting for Tag detection, continuously polling for presence check, read & write data.
BaseActivity does initialization of ANFCManager with other required work such as pending restart Intent, check nfc adapter, enableForegroundDispatch, ...
private fun initField() {
mNfcManager = ANfcManager(this)
}
private fun createPendingRestartIntent() {
pendingIntent = PendingIntent.getActivity(this, 0, Intent(this, javaClass)
.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0
)
}
override fun onResume() {
super.onResume()
try {
if(mNfcManager.checkNfcPowerStatus()) // NfcAdapter enabled or not
setReadyToHandleTag()
else Log.w(TAG, "Nfc is not supported or disabled.")
} catch (e: AcmNfcManager.NfcNotEnabledException) {
Log.e(TAG, "Nfc not enabled", e)
}
}
private fun setReadyToHandleTag() {
try {
TECHLISTS = arrayOf(arrayOf(IsoDep::class.java.name), arrayOf(NfcV::class.java.name),
arrayOf(NfcA::class.java.name), arrayOf(NfcB::class.java.name),
arrayOf(NfcF::class.java.name),arrayOf(Ndef::class.java.name),
arrayOf(NdefFormatable::class.java.name))
val tagDetected = IntentFilter(NfcAdapter.ACTION_TECH_DISCOVERED)
tagDetected.addCategory(Intent.CATEGORY_DEFAULT)
TAGFILTERS = arrayOf(tagDetected)
} catch (e: Exception) {
Log.e(TAG, "TECH or TAG filter no detected!!!" )
}
pendingIntent?.let { mNfcManager.enableForegroundDispatch(this, it, TAGFILTERS, TECHLISTS) }
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
nfcState = mNfcManager.filterIntent(intent)
dispatchActionOnTag(nfcState)
}
// this abs function will provide the Tag state in the corresponding class
abstract fun dispatchActionOnTag(tag: Boolean)
Each Activity has NfcListener for tag detection and will do the read/write using ANfcManager API's. Also to continuously checking the tag presence, using handler with looper internal class inside NFC Manager for presence check.
Here is the function inside ActivityA which trigger the method after tag detection as well as presence check thread,
override fun dispatchActionOnTag(tag: Boolean) {
mNfcStatus = tag
if (nfcStateListener() != null) {
nfcStateListener().updateNfcState(tag)
mNfcManager.startTagCheck() // presence check handler every x sec
}
}
This same function is been repeated(kind of not clean but still works) in each of the activity for tag detection and presence check & based on that read/write data to the Tag.
Here comes my problem,
Preconditions :
Tag in my application(product) is at a fixed location(sticked in a hardware) & is not usually taken out unless there is a tag change.
There are situations where Tag can be taken out in mostly ActivityB or ActivityC activity will be running, which required to repeat the same callback code in these activities.
Required:
- When switching from ActvityA-> ActivityB, Tag detection flow is not done(onNewIntent) or TAg is not taken out from proximity and tapped again. How will I write/read data to the tag?
ANFCManager,
class ANfcManager #Inject constructor(context: Context) {
private val mContext = context
private lateinit var nfcAdapter: NfcAdapter
private lateinit var mTag: Tag
private lateinit var iCodeTag: ICodeSlix2
private lateinit var icode: ICode
init {
val readPermission = ContextCompat.checkSelfPermission(
mContext,
Manifest.permission.WRITE_EXTERNAL_STORAGE
) == PackageManager.PERMISSION_GRANTED
if (!readPermission) {
ActivityCompat.requestPermissions(
mContext as Activity,
arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), 113
)
}
/**
* initialize background thread for presence check every x seconds.
*/
val thread = HandlerThread("PresenceCheckThread")
thread.start()
mHandler = PresenceHandler(thread.looper)
}
fun enableForegroundDispatch(
activity: FragmentActivity, intent: PendingIntent,
filters: Array<IntentFilter>?, techLists: Array<Array<String>>?
) {
nfcAdapter.enableForegroundDispatch(activity, intent, filters, techLists)
}
fun disableForegroundDispatch(activity: Activity) {
nfcAdapter.disableForegroundDispatch(activity)
}
fun filterIntent(intent: Intent): Boolean {
val action = intent.action
if (NfcAdapter.ACTION_TECH_DISCOVERED == action
|| NfcAdapter.ACTION_TAG_DISCOVERED == action
|| NfcAdapter.ACTION_NDEF_DISCOVERED == action
) {
if (intent.hasExtra(NfcAdapter.EXTRA_TAG)) {
mTag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG)!!
return if (discoverTag()) {
Toast.makeText(mContext, "Tag detected.", Toast.LENGTH_SHORT).show()
true
} else {
ignoreTag()
false
}
}
}
return false
}
/**
* discover the Tag family.
*/
fun discoverTag(): Boolean {
icode = getTag(mTag)
if (ICodeSlix2::class.java.isInstance(icode))
iCodeTag = icode as ICodeSlix2
return iCodeTag != null
}
fun checkNfcPowerStatus(): Boolean {
return checkNfcPowerStatus(mContext)
}
/**
* Check Nfc status
*/
private fun checkNfcPowerStatus(context: Context?): Boolean {
nfcAdapter = NfcAdapter.getDefaultAdapter(context)
var enabled = false
if (nfcAdapter != null) {
enabled = nfcAdapter.isEnabled
}
return enabled
}
fun writeUpdateBlocks() {
try {
iCodeTag.connect()
.
. // proprietary code
.
}catch (e: IOException) {
e.printStackTrace()
Log.e(TAG, "IOException: ", e)
} catch (e: SmartCardException) {
e.printStackTrace()
Log.e(TAG, "SmartCardException: ", e)
} catch (e: IllegalArgumentException) {
e.printStackTrace()
Log.e(TAG, "IllegalArgumentException: ", e)
} catch (e: IllegalStateException) {
e.printStackTrace()
Log.e(TAG, "IllegalArgumentException: ", e)
} catch (e: IndexOutOfBoundsException) {
e.printStackTrace()
Log.e(TAG, "IndexOutOfBoundsException: ", e)
} finally {
iCodeTag.close()
}
}
Required: - When switching from ActvityA-> ActivityB, Tag detection
flow is not done(onNewIntent) or TAg is not taken out from proximity
and tapped again. How will I write/read data to the tag?
So the Tag object is a Parcelable Object , just pass it from ActivityA to ActivityB, you don't need to re-discover it.
e.g. something like (sorry in Java not Kotlin)
ActivityA
Intent intent = new Intent(getActivity(), ActivityB.class);
intent.putExtra("TAG", mTag);
startActivity(intent);
The in ActivityB onCreate
Intent intent = getIntent();
mTag = intent.getParcelableExtra("TAG")
// Start doing stuff with the Tag just like if you got it via discovery
// ANfcManager might need a `setTag` method to set it without discovery.
// or allow a Tag be be passed in the ANfcManager constructor
Not that I would use enableForegroundDispatch for reading and especially writing to Tags as I found it too unreliable, I would recommend enableReaderMode but then you can still pass the Tag Object between activities.
It was quick to convert the Manager class as Singleton and rest all remain same.
BaseActivity,
fun initField() {
mNfcManager = ANfcManager.getInstance(this)
}
class ANfcManager private constructor(context: Context){
companion object : SingletonHolder<ANfcManager, Context>(::ANfcManager) {
val TAG = ANfcManager::class.java.simpleName
}
init{
mContext = context
.
.
.
}
}

Categories

Resources