Skip to content
This repository has been archived by the owner on Aug 23, 2023. It is now read-only.

Commit

Permalink
Merge pull request #223 from android/permissions
Browse files Browse the repository at this point in the history
Adds Fine permission UI to background permission sample.
  • Loading branch information
codingjeremy authored Feb 28, 2020
2 parents eb5be06 + 75d2e1c commit 8dd7b06
Show file tree
Hide file tree
Showing 9 changed files with 400 additions and 21 deletions.
13 changes: 13 additions & 0 deletions LocationUpdatesBackgroundKotlin/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,12 @@ apply plugin: 'kotlin-android'

apply plugin: 'kotlin-android-extensions'

apply plugin: 'kotlin-kapt'

android {
compileSdkVersion 29
buildToolsVersion "29.0.2"

defaultConfig {
applicationId "com.google.android.gms.location.sample.locationupdatesbackgroundkotlin"
minSdkVersion 15
Expand All @@ -30,12 +33,17 @@ android {
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}

buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}

dataBinding {
enabled true
}
}

dependencies {
Expand All @@ -44,7 +52,12 @@ dependencies {
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.core:core-ktx:1.2.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'

kapt 'com.android.databinding:compiler:3.1.4'

testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
implementation 'com.google.android.material:material:1.1.0'
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,19 @@
limitations under the License.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.google.android.gms.location.sample.locationupdatesbackgroundkotlin">

<uses-permission android:name="android.permission.ACCESS_FINE_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">
android:theme="@style/AppTheme"
tools:ignore="AllowBackup,GoogleAppIndexingWarning">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,55 @@ package com.google.android.gms.location.sample.locationupdatesbackgroundkotlin

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.databinding.DataBindingUtil.setContentView

class MainActivity : AppCompatActivity() {


import com.google.android.gms.location.sample.locationupdatesbackgroundkotlin.PermissionRequestFragment.Callbacks
import com.google.android.gms.location.sample.locationupdatesbackgroundkotlin.databinding.ActivityMainBinding


/**
* This app allows a user to track their location in the background.
*
* IMPORTANT NOTE: You should always prefer 'while-in-use' location tracking, i.e., track location
* while the app is in use and create a foreground service (tied to a Notification) when the
* user navigates away from the app.
*
* If you do have an approved use case for tracking location in the background, it will require an
* additional permission.
*
* Note: Users have four options in Android 11+ regarding location:
*
* * One time only
* * Allow while app is in use, i.e., while app is in foreground
* * Allow all the time
* * Not allow location at all
*
* Best practice requires you spread out your first fine/course request and your background request.
*/
class MainActivity : AppCompatActivity(), Callbacks {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

setContentView<ActivityMainBinding>(this, R.layout.activity_main)

val currentFragment = supportFragmentManager.findFragmentById(R.id.fragment_container)

if (currentFragment == null) {

val fragment = PermissionRequestFragment.newInstance(PermissionRequestType.FINE_LOCATION)

supportFragmentManager
.beginTransaction()
.replace(R.id.fragment_container, fragment)
.addToBackStack(null)
.commit()
}
}

override fun displayLocationUI() {
TODO("Add redirect to main app fragment")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
/*
* Copyright (C) 2020 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.gms.location.sample.locationupdatesbackgroundkotlin

import android.Manifest
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Bundle
import android.provider.Settings
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.content.ContextCompat.checkSelfPermission
import androidx.fragment.app.Fragment

import com.google.android.gms.location.sample.locationupdatesbackgroundkotlin.databinding.FragmentPermissionRequestBinding
import com.google.android.material.snackbar.Snackbar

private const val TAG = "PermissionRequestFrag"

/**
* Displays information about why a user should enable either the FINE location permission or the
* background location permission (depending on what is needed).
*
* Allows users to grant the permissions as well.
*/
class PermissionRequestFragment : Fragment() {

// Set by Activity for which type of permission to request (Fine or background).
private var permissionRequestType: PermissionRequestType? = null

private lateinit var binding: FragmentPermissionRequestBinding

private var activityListener: Callbacks? = null


override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

permissionRequestType =
arguments?.getSerializable(ARG_PERMISSION_REQUEST_TYPE) as PermissionRequestType
}

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {

binding = FragmentPermissionRequestBinding.inflate(inflater, container, false)

when (permissionRequestType) {
PermissionRequestType.FINE_LOCATION ->
binding.explanationTextView.text =
getString(R.string.fine_location_access_rationale_text)

PermissionRequestType.BACKGROUND_LOCATION ->
binding.explanationTextView.text =
getString(R.string.background_location_access_rationale_text)
}

binding.permissionRequestButton.setOnClickListener {
when (permissionRequestType) {
PermissionRequestType.FINE_LOCATION ->
requestFineLocationPermission()

PermissionRequestType.BACKGROUND_LOCATION ->
TODO("Add system background permission request.")
}
}

return binding.root
}

override fun onAttach(context: Context) {
super.onAttach(context)

if (context is Callbacks) {
activityListener = context
} else {
throw RuntimeException("$context must implement OnFragmentInteractionListener")
}
}

override fun onDetach() {
super.onDetach()

activityListener = null
}

override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<String>,
grantResults: IntArray
) {
Log.d(TAG, "onRequestPermissionResult")

when (requestCode) {
REQUEST_FINE_LOCATION_PERMISSIONS_REQUEST_CODE -> when {
grantResults.isEmpty() ->
// If user interaction was interrupted, the permission request
// is cancelled and you receive an empty array.
Log.d(TAG, "User interaction was cancelled.")

grantResults[0] == PackageManager.PERMISSION_GRANTED ->
activityListener?.displayLocationUI()

else -> {

Snackbar.make(
binding.frameLayout,
R.string.permission_denied_explanation,
Snackbar.LENGTH_LONG
)
.setAction(R.string.settings) {
// Build intent that displays the App settings screen.
val intent = Intent()
intent.action = 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()
}
}
}
}

private fun fineLocationPermissionApproved(): Boolean {

val context = context ?: return false

return PackageManager.PERMISSION_GRANTED == checkSelfPermission(
context,
Manifest.permission.ACCESS_FINE_LOCATION
)
}

private fun requestFineLocationPermission() {

if (fineLocationPermissionApproved()) {
activityListener?.displayLocationUI()

} else {

val provideRationale = shouldShowRequestPermissionRationale(
Manifest.permission.ACCESS_FINE_LOCATION
)

// If the user denied a previous request, but didn't check "Don't ask again", provide
// additional rationale.
if (provideRationale) {
Snackbar.make(
binding.frameLayout,
R.string.simple_permission_rationale,
Snackbar.LENGTH_LONG
)
.setAction(R.string.ok) {
requestPermissions(
arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
REQUEST_FINE_LOCATION_PERMISSIONS_REQUEST_CODE
)
}
.show()
} else {
Log.d(TAG, "Request fine location permission")
requestPermissions(
arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
REQUEST_FINE_LOCATION_PERMISSIONS_REQUEST_CODE
)
}
}
}


/**
* This interface must be implemented by activities that contain this
* fragment to allow an interaction in this fragment to be communicated
* to the activity and potentially other fragments contained in that
* activity.
*
*
* See the Android Training lesson [Communicating with Other Fragments]
* (http://developer.android.com/training/basics/fragments/communicating.html)
* for more information.
*/
interface Callbacks {
fun displayLocationUI()
}

companion object {
private const val ARG_PERMISSION_REQUEST_TYPE =
"com.google.android.gms.location.sample.locationupdatesbackgroundkotlin.PERMISSION_REQUEST_TYPE"

private const val REQUEST_FINE_LOCATION_PERMISSIONS_REQUEST_CODE = 34

/**
* Use this factory method to create a new instance of
* this fragment using the provided parameters.
*
* @param permissionRequestType Type of permission you would like to request.
* @return A new instance of fragment PermissionRequestFragment.
*/
@JvmStatic
fun newInstance(permissionRequestType: PermissionRequestType) =
PermissionRequestFragment().apply {
arguments = Bundle().apply {
putSerializable(ARG_PERMISSION_REQUEST_TYPE, permissionRequestType)
}
}
}
}

enum class PermissionRequestType {
FINE_LOCATION, BACKGROUND_LOCATION
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,16 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<layout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

</androidx.constraintlayout.widget.ConstraintLayout>
<FrameLayout
android:id="@+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
</FrameLayout>

</layout>
Loading

0 comments on commit 8dd7b06

Please sign in to comment.