Skip to content

Location: Implement settings request #2216

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions play-services-location/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,6 @@ dependencies {
api project(':play-services-base')
api project(':play-services-basement')
api project(':play-services-tasks')

annotationProcessor project(':safe-parcel-processor')
}
1 change: 1 addition & 0 deletions play-services-location/core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ dependencies {
implementation project(':play-services-base-core')
implementation project(':play-services-location-core-base')

implementation "androidx.core:core-ktx:$coreVersion"
implementation "androidx.lifecycle:lifecycle-service:$lifecycleVersion"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycleVersion"
implementation "androidx.navigation:navigation-fragment-ktx:$navigationVersion"
Expand Down
13 changes: 13 additions & 0 deletions play-services-location/core/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,19 @@
tools:ignore="ProtectedPermissions" />

<application>

<activity
android:name="org.microg.gms.location.settings.LocationSettingsCheckerActivity"
android:excludeFromRecents="true"
android:exported="false"
android:process=":ui"
android:theme="@style/AlertThemeSelector">
<intent-filter android:priority="-1">
<action android:name="com.google.android.gms.location.settings.CHECK_SETTINGS" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>

<activity
android:name="org.microg.gms.location.manager.AskPermissionActivity"
android:excludeFromRecents="true"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ package org.microg.gms.location.manager
import android.Manifest
import android.app.Activity
import android.app.PendingIntent
import android.app.PendingIntent.FLAG_MUTABLE
import android.app.PendingIntent.FLAG_UPDATE_CURRENT
import android.content.Context
import android.content.Intent
Expand All @@ -17,6 +16,7 @@ import android.location.Location
import android.os.*
import android.os.Build.VERSION.SDK_INT
import android.util.Log
import androidx.core.app.PendingIntentCompat
import androidx.core.content.ContextCompat
import androidx.core.content.getSystemService
import androidx.core.location.LocationListenerCompat
Expand Down Expand Up @@ -146,7 +146,7 @@ class LocationManager(private val context: Context, override val lifecycle: Life
}
val intent = Intent(context, LocationManagerService::class.java)
intent.action = LocationManagerService.ACTION_REPORT_LOCATION
coarsePendingIntent = PendingIntent.getService(context, 0, intent, (if (SDK_INT >= 31) FLAG_MUTABLE else 0) or FLAG_UPDATE_CURRENT)
coarsePendingIntent = PendingIntentCompat.getService(context, 0, intent, FLAG_UPDATE_CURRENT, true)
lastLocationCapsule.start()
requestManager.start()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,22 @@ package org.microg.gms.location.manager

import android.Manifest.permission.*
import android.app.PendingIntent
import android.bluetooth.BluetoothManager
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.content.pm.PackageManager.PERMISSION_GRANTED
import android.location.Location
import android.location.LocationManager.GPS_PROVIDER
import android.location.LocationManager.NETWORK_PROVIDER
import android.os.Binder
import android.os.Build.VERSION.SDK_INT
import android.os.IBinder
import android.os.Parcel
import android.os.SystemClock
import android.provider.Settings
import android.util.Log
import androidx.core.app.PendingIntentCompat
import androidx.core.content.getSystemService
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
Expand All @@ -25,13 +31,14 @@ import com.google.android.gms.common.api.CommonStatusCodes
import com.google.android.gms.common.api.Status
import com.google.android.gms.common.api.internal.IStatusCallback
import com.google.android.gms.common.internal.ICancelToken
import com.google.android.gms.common.internal.safeparcel.SafeParcelableSerializer
import com.google.android.gms.location.*
import com.google.android.gms.location.internal.*
import com.google.android.gms.location.internal.DeviceOrientationRequestUpdateData.REMOVE_UPDATES
import com.google.android.gms.location.internal.DeviceOrientationRequestUpdateData.REQUEST_UPDATES
import kotlinx.coroutines.*
import org.microg.gms.common.NonCancelToken
import org.microg.gms.location.hasNetworkLocationServiceBuiltIn
import org.microg.gms.location.settings.*
import org.microg.gms.utils.warnOnTransactionIssues

class LocationManagerInstance(
Expand Down Expand Up @@ -209,17 +216,36 @@ class LocationManagerInstance(
}

override fun requestLocationSettingsDialog(settingsRequest: LocationSettingsRequest?, callback: ISettingsCallbacks?, packageName: String?) {
Log.d(TAG, "requestLocationSettingsDialog by ${getClientIdentity().packageName}")
Log.d(TAG, "requestLocationSettingsDialog by ${getClientIdentity().packageName} $settingsRequest")
val clientIdentity = getClientIdentity()
lifecycleScope.launchWhenStarted {
val locationManager = context.getSystemService<android.location.LocationManager>()
val gpsPresent = locationManager?.allProviders?.contains(GPS_PROVIDER) == true
val networkPresent = locationManager?.allProviders?.contains(NETWORK_PROVIDER) == true || context.hasNetworkLocationServiceBuiltIn()
val gpsUsable = gpsPresent && locationManager?.isProviderEnabled(GPS_PROVIDER) == true &&
context.packageManager.checkPermission(ACCESS_FINE_LOCATION, clientIdentity.packageName) == PERMISSION_GRANTED
val networkUsable = networkPresent && locationManager?.isProviderEnabled(NETWORK_PROVIDER) == true &&
context.packageManager.checkPermission(ACCESS_COARSE_LOCATION, clientIdentity.packageName) == PERMISSION_GRANTED
runCatching { callback?.onLocationSettingsResult(LocationSettingsResult(LocationSettingsStates(gpsUsable, networkUsable, false, gpsPresent, networkPresent, true), Status.SUCCESS)) }
val states = context.getDetailedLocationSettingsStates()
val requests = settingsRequest?.requests?.map {
it.priority to (if (it.granularity == Granularity.GRANULARITY_PERMISSION_LEVEL) context.granularityFromPermission(clientIdentity) else it.granularity)
}.orEmpty()
val gpsRequested = requests.any { it.first == Priority.PRIORITY_HIGH_ACCURACY && it.second == Granularity.GRANULARITY_FINE }
val networkLocationRequested = requests.any { it.first <= Priority.PRIORITY_LOW_POWER && it.second >= Granularity.GRANULARITY_COARSE }
val bleRequested = settingsRequest?.needBle == true
val statusCode = when {
gpsRequested && states.gpsPresent && !states.gpsUsable -> CommonStatusCodes.RESOLUTION_REQUIRED
networkLocationRequested && states.networkLocationPresent && !states.networkLocationUsable -> CommonStatusCodes.RESOLUTION_REQUIRED
bleRequested && states.blePresent && !states.bleUsable -> CommonStatusCodes.RESOLUTION_REQUIRED
gpsRequested && !states.gpsPresent -> LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE
networkLocationRequested && !states.networkLocationPresent -> LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE
bleRequested && !states.blePresent -> LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE
else -> CommonStatusCodes.SUCCESS
}

val resolution = if (statusCode == CommonStatusCodes.RESOLUTION_REQUIRED) {
val intent = Intent(ACTION_LOCATION_SETTINGS_CHECKER)
intent.setPackage(context.packageName)
intent.putExtra(EXTRA_ORIGINAL_PACKAGE_NAME, clientIdentity.packageName)
intent.putExtra(EXTRA_SETTINGS_REQUEST, SafeParcelableSerializer.serializeToBytes(settingsRequest))
PendingIntentCompat.getActivity(context, clientIdentity.packageName.hashCode(), intent, PendingIntent.FLAG_UPDATE_CURRENT, true)
} else null
val status = Status(statusCode, LocationSettingsStatusCodes.getStatusCodeString(statusCode), resolution)
Log.d(TAG, "requestLocationSettingsDialog by ${getClientIdentity().packageName} returns $status")
runCatching { callback?.onLocationSettingsResult(LocationSettingsResult(status, states.toApi())) }
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* SPDX-FileCopyrightText: 2024 microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/

package org.microg.gms.location.settings

import android.Manifest.permission.*
import android.bluetooth.BluetoothManager
import android.content.Context
import android.content.pm.PackageManager.*
import android.location.LocationManager
import android.location.LocationManager.GPS_PROVIDER
import android.location.LocationManager.NETWORK_PROVIDER
import android.os.Build.VERSION.SDK_INT
import android.provider.Settings
import androidx.core.content.getSystemService
import com.google.android.gms.location.LocationSettingsStates
import org.microg.gms.location.hasNetworkLocationServiceBuiltIn

data class DetailedLocationSettingsStates(
val gpsSystemFeature: Boolean,
val networkLocationSystemFeature: Boolean,
val bluetoothLeSystemFeature: Boolean,
val gpsProviderEnabled: Boolean,
val networkLocationProviderEnabled: Boolean,
val networkLocationProviderBuiltIn: Boolean,
val fineLocationPermission: Boolean,
val coarseLocationPermission: Boolean,
val backgroundLocationPermission: Boolean,
val blePresent: Boolean,
val bleEnabled: Boolean,
val bleScanAlways: Boolean,
val airplaneMode: Boolean,
) {
val gpsPresent: Boolean
get() = gpsSystemFeature
val networkLocationPresent: Boolean
get() = networkLocationSystemFeature || networkLocationProviderBuiltIn
val gpsUsable: Boolean
get() = gpsProviderEnabled && fineLocationPermission && backgroundLocationPermission
val networkLocationUsable: Boolean
get() = (networkLocationProviderEnabled || networkLocationProviderBuiltIn) && coarseLocationPermission && backgroundLocationPermission
val bleUsable: Boolean
get() = blePresent && (bleEnabled || (bleScanAlways && !airplaneMode))

fun toApi() = LocationSettingsStates(gpsUsable, networkLocationUsable, bleUsable, gpsPresent, networkLocationPresent, blePresent)
}

fun Context.getDetailedLocationSettingsStates(): DetailedLocationSettingsStates {
val bluetoothLeSystemFeature = packageManager.hasSystemFeature(FEATURE_BLUETOOTH_LE)
val locationManager = getSystemService<LocationManager>()
val bluetoothManager = if (bluetoothLeSystemFeature) getSystemService<BluetoothManager>() else null
val bleAdapter = bluetoothManager?.adapter

return DetailedLocationSettingsStates(
gpsSystemFeature = packageManager.hasSystemFeature(FEATURE_LOCATION_GPS),
networkLocationSystemFeature = packageManager.hasSystemFeature(FEATURE_LOCATION_NETWORK),
bluetoothLeSystemFeature = bluetoothLeSystemFeature,
gpsProviderEnabled = locationManager?.isProviderEnabled(GPS_PROVIDER) == true,
networkLocationProviderEnabled = locationManager?.isProviderEnabled(NETWORK_PROVIDER) == true,
networkLocationProviderBuiltIn = hasNetworkLocationServiceBuiltIn(),
fineLocationPermission = packageManager.checkPermission(ACCESS_FINE_LOCATION, packageName) == PERMISSION_GRANTED,
coarseLocationPermission = packageManager.checkPermission(ACCESS_COARSE_LOCATION, packageName) == PERMISSION_GRANTED,
backgroundLocationPermission = if (SDK_INT < 29) true else
packageManager.checkPermission(ACCESS_BACKGROUND_LOCATION, packageName) == PERMISSION_GRANTED,
blePresent = bleAdapter != null,
bleEnabled = bleAdapter?.isEnabled == true,
bleScanAlways = Settings.Global.getInt(contentResolver, "ble_scan_always_enabled", 0) == 1,
airplaneMode = Settings.Global.getInt(contentResolver, Settings.Global.AIRPLANE_MODE_ON, 0) != 0
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* SPDX-FileCopyrightText: 2023 microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/

package org.microg.gms.location.settings

import android.app.Activity
import android.content.Context
import android.content.Intent
import android.location.LocationManager
import android.os.Bundle
import android.provider.Settings
import android.util.Log
import android.widget.TextView
import org.microg.gms.location.core.R

const val ACTION_LOCATION_SETTINGS_CHECKER = "com.google.android.gms.location.settings.CHECK_SETTINGS"

private const val REQUEST_CODE_LOCATION = 120
const val EXTRA_ORIGINAL_PACKAGE_NAME = "originalPackageName"
const val EXTRA_SETTINGS_REQUEST = "locationSettingsRequests"

class LocationSettingsCheckerActivity : Activity() {

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

findViewById<TextView>(R.id.location_setting_checker_sure).setOnClickListener {
// TODO: We also should handle permissions, Airplane Mode and BLE here.
val intent = Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS)
startActivityForResult(intent, REQUEST_CODE_LOCATION)
}
findViewById<TextView>(R.id.location_setting_checker_cancel).setOnClickListener {
checkerBack(RESULT_CANCELED)
}
}

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == REQUEST_CODE_LOCATION && isLocationEnabled(this)) {
checkerBack(RESULT_OK)
} else {
super.onActivityResult(requestCode, resultCode, data)
}
}

override fun onBackPressed() {
checkerBack(RESULT_CANCELED)
}

private fun checkerBack(resultCode: Int) {
setResult(resultCode)
finish()
}

private fun isLocationEnabled(context: Context): Boolean {
val locationManager = context.getSystemService(LOCATION_SERVICE) as LocationManager
return locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER) || locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#2a2931" />
<corners android:radius="20dp" />
</shape>
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingHorizontal="20dp"
android:paddingTop="16dp">

<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/location_settings_dialog_message_title_to_continue"
android:textColor="#e4e1e5"
android:textSize="16sp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginVertical="10dp"
android:text="@string/location_settings_dialog_message_details_start_paragraph"
android:textColor="#e4e1e5"
android:textSize="13sp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginVertical="8dp"
android:layout_marginStart="30dp"
android:text="@string/location_settings_dialog_message_location_services_gps_and_nlp"
android:textColor="#e4e1e5"
android:textSize="13sp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginVertical="8dp"
android:layout_marginStart="30dp"
android:text="@string/location_settings_dialog_message_gls_consent"
android:textColor="#e4e1e5"
android:textSize="13sp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginVertical="10dp"
android:text="@string/location_settings_dialog_message_details_end_paragraph"
android:textColor="#e4e1e5"
android:textSize="13sp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:layout_marginEnd="10dp"
android:layout_marginBottom="24dp"
android:gravity="right"
android:orientation="horizontal">

<TextView
android:id="@+id/location_setting_checker_cancel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/location_settings_dialog_btn_cancel"
android:textColor="#c0c1fa"
android:textSize="13sp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<TextView
android:id="@+id/location_setting_checker_sure"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="30dp"
android:text="@string/location_settings_dialog_btn_sure"
android:textColor="#c0c1fa"
android:textSize="13sp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />

</LinearLayout>

</LinearLayout>
Loading