Skip to content

Commit 2e4d3ae

Browse files
Location: Implement settings request (#2216)
For some applications that require GPS function, detect and remind the GPS to check in, such as Google Maps Co-authored-by: Marvin W <[email protected]>
1 parent fb8c497 commit 2e4d3ae

File tree

20 files changed

+515
-93
lines changed

20 files changed

+515
-93
lines changed

play-services-location/build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,6 @@ dependencies {
3838
api project(':play-services-base')
3939
api project(':play-services-basement')
4040
api project(':play-services-tasks')
41+
42+
annotationProcessor project(':safe-parcel-processor')
4143
}

play-services-location/core/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ dependencies {
1111
implementation project(':play-services-base-core')
1212
implementation project(':play-services-location-core-base')
1313

14+
implementation "androidx.core:core-ktx:$coreVersion"
1415
implementation "androidx.lifecycle:lifecycle-service:$lifecycleVersion"
1516
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycleVersion"
1617
implementation "androidx.navigation:navigation-fragment-ktx:$navigationVersion"

play-services-location/core/src/main/AndroidManifest.xml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,19 @@
2121
tools:ignore="ProtectedPermissions" />
2222

2323
<application>
24+
25+
<activity
26+
android:name="org.microg.gms.location.settings.LocationSettingsCheckerActivity"
27+
android:excludeFromRecents="true"
28+
android:exported="false"
29+
android:process=":ui"
30+
android:theme="@style/AlertThemeSelector">
31+
<intent-filter android:priority="-1">
32+
<action android:name="com.google.android.gms.location.settings.CHECK_SETTINGS" />
33+
<category android:name="android.intent.category.DEFAULT" />
34+
</intent-filter>
35+
</activity>
36+
2437
<activity
2538
android:name="org.microg.gms.location.manager.AskPermissionActivity"
2639
android:excludeFromRecents="true"

play-services-location/core/src/main/kotlin/org/microg/gms/location/manager/LocationManager.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ package org.microg.gms.location.manager
88
import android.Manifest
99
import android.app.Activity
1010
import android.app.PendingIntent
11-
import android.app.PendingIntent.FLAG_MUTABLE
1211
import android.app.PendingIntent.FLAG_UPDATE_CURRENT
1312
import android.content.Context
1413
import android.content.Intent
@@ -17,6 +16,7 @@ import android.location.Location
1716
import android.os.*
1817
import android.os.Build.VERSION.SDK_INT
1918
import android.util.Log
19+
import androidx.core.app.PendingIntentCompat
2020
import androidx.core.content.ContextCompat
2121
import androidx.core.content.getSystemService
2222
import androidx.core.location.LocationListenerCompat
@@ -146,7 +146,7 @@ class LocationManager(private val context: Context, override val lifecycle: Life
146146
}
147147
val intent = Intent(context, LocationManagerService::class.java)
148148
intent.action = LocationManagerService.ACTION_REPORT_LOCATION
149-
coarsePendingIntent = PendingIntent.getService(context, 0, intent, (if (SDK_INT >= 31) FLAG_MUTABLE else 0) or FLAG_UPDATE_CURRENT)
149+
coarsePendingIntent = PendingIntentCompat.getService(context, 0, intent, FLAG_UPDATE_CURRENT, true)
150150
lastLocationCapsule.start()
151151
requestManager.start()
152152
}

play-services-location/core/src/main/kotlin/org/microg/gms/location/manager/LocationManagerInstance.kt

Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,22 @@ package org.microg.gms.location.manager
77

88
import android.Manifest.permission.*
99
import android.app.PendingIntent
10+
import android.bluetooth.BluetoothManager
1011
import android.content.Context
12+
import android.content.Intent
13+
import android.content.pm.PackageManager
1114
import android.content.pm.PackageManager.PERMISSION_GRANTED
1215
import android.location.Location
1316
import android.location.LocationManager.GPS_PROVIDER
1417
import android.location.LocationManager.NETWORK_PROVIDER
1518
import android.os.Binder
19+
import android.os.Build.VERSION.SDK_INT
1620
import android.os.IBinder
1721
import android.os.Parcel
1822
import android.os.SystemClock
23+
import android.provider.Settings
1924
import android.util.Log
25+
import androidx.core.app.PendingIntentCompat
2026
import androidx.core.content.getSystemService
2127
import androidx.lifecycle.Lifecycle
2228
import androidx.lifecycle.LifecycleOwner
@@ -25,13 +31,14 @@ import com.google.android.gms.common.api.CommonStatusCodes
2531
import com.google.android.gms.common.api.Status
2632
import com.google.android.gms.common.api.internal.IStatusCallback
2733
import com.google.android.gms.common.internal.ICancelToken
34+
import com.google.android.gms.common.internal.safeparcel.SafeParcelableSerializer
2835
import com.google.android.gms.location.*
2936
import com.google.android.gms.location.internal.*
3037
import com.google.android.gms.location.internal.DeviceOrientationRequestUpdateData.REMOVE_UPDATES
3138
import com.google.android.gms.location.internal.DeviceOrientationRequestUpdateData.REQUEST_UPDATES
3239
import kotlinx.coroutines.*
33-
import org.microg.gms.common.NonCancelToken
3440
import org.microg.gms.location.hasNetworkLocationServiceBuiltIn
41+
import org.microg.gms.location.settings.*
3542
import org.microg.gms.utils.warnOnTransactionIssues
3643

3744
class LocationManagerInstance(
@@ -209,17 +216,36 @@ class LocationManagerInstance(
209216
}
210217

211218
override fun requestLocationSettingsDialog(settingsRequest: LocationSettingsRequest?, callback: ISettingsCallbacks?, packageName: String?) {
212-
Log.d(TAG, "requestLocationSettingsDialog by ${getClientIdentity().packageName}")
219+
Log.d(TAG, "requestLocationSettingsDialog by ${getClientIdentity().packageName} $settingsRequest")
213220
val clientIdentity = getClientIdentity()
214221
lifecycleScope.launchWhenStarted {
215-
val locationManager = context.getSystemService<android.location.LocationManager>()
216-
val gpsPresent = locationManager?.allProviders?.contains(GPS_PROVIDER) == true
217-
val networkPresent = locationManager?.allProviders?.contains(NETWORK_PROVIDER) == true || context.hasNetworkLocationServiceBuiltIn()
218-
val gpsUsable = gpsPresent && locationManager?.isProviderEnabled(GPS_PROVIDER) == true &&
219-
context.packageManager.checkPermission(ACCESS_FINE_LOCATION, clientIdentity.packageName) == PERMISSION_GRANTED
220-
val networkUsable = networkPresent && locationManager?.isProviderEnabled(NETWORK_PROVIDER) == true &&
221-
context.packageManager.checkPermission(ACCESS_COARSE_LOCATION, clientIdentity.packageName) == PERMISSION_GRANTED
222-
runCatching { callback?.onLocationSettingsResult(LocationSettingsResult(LocationSettingsStates(gpsUsable, networkUsable, false, gpsPresent, networkPresent, true), Status.SUCCESS)) }
222+
val states = context.getDetailedLocationSettingsStates()
223+
val requests = settingsRequest?.requests?.map {
224+
it.priority to (if (it.granularity == Granularity.GRANULARITY_PERMISSION_LEVEL) context.granularityFromPermission(clientIdentity) else it.granularity)
225+
}.orEmpty()
226+
val gpsRequested = requests.any { it.first == Priority.PRIORITY_HIGH_ACCURACY && it.second == Granularity.GRANULARITY_FINE }
227+
val networkLocationRequested = requests.any { it.first <= Priority.PRIORITY_LOW_POWER && it.second >= Granularity.GRANULARITY_COARSE }
228+
val bleRequested = settingsRequest?.needBle == true
229+
val statusCode = when {
230+
gpsRequested && states.gpsPresent && !states.gpsUsable -> CommonStatusCodes.RESOLUTION_REQUIRED
231+
networkLocationRequested && states.networkLocationPresent && !states.networkLocationUsable -> CommonStatusCodes.RESOLUTION_REQUIRED
232+
bleRequested && states.blePresent && !states.bleUsable -> CommonStatusCodes.RESOLUTION_REQUIRED
233+
gpsRequested && !states.gpsPresent -> LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE
234+
networkLocationRequested && !states.networkLocationPresent -> LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE
235+
bleRequested && !states.blePresent -> LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE
236+
else -> CommonStatusCodes.SUCCESS
237+
}
238+
239+
val resolution = if (statusCode == CommonStatusCodes.RESOLUTION_REQUIRED) {
240+
val intent = Intent(ACTION_LOCATION_SETTINGS_CHECKER)
241+
intent.setPackage(context.packageName)
242+
intent.putExtra(EXTRA_ORIGINAL_PACKAGE_NAME, clientIdentity.packageName)
243+
intent.putExtra(EXTRA_SETTINGS_REQUEST, SafeParcelableSerializer.serializeToBytes(settingsRequest))
244+
PendingIntentCompat.getActivity(context, clientIdentity.packageName.hashCode(), intent, PendingIntent.FLAG_UPDATE_CURRENT, true)
245+
} else null
246+
val status = Status(statusCode, LocationSettingsStatusCodes.getStatusCodeString(statusCode), resolution)
247+
Log.d(TAG, "requestLocationSettingsDialog by ${getClientIdentity().packageName} returns $status")
248+
runCatching { callback?.onLocationSettingsResult(LocationSettingsResult(status, states.toApi())) }
223249
}
224250
}
225251

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/*
2+
* SPDX-FileCopyrightText: 2024 microG Project Team
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package org.microg.gms.location.settings
7+
8+
import android.Manifest.permission.*
9+
import android.bluetooth.BluetoothManager
10+
import android.content.Context
11+
import android.content.pm.PackageManager.*
12+
import android.location.LocationManager
13+
import android.location.LocationManager.GPS_PROVIDER
14+
import android.location.LocationManager.NETWORK_PROVIDER
15+
import android.os.Build.VERSION.SDK_INT
16+
import android.provider.Settings
17+
import androidx.core.content.getSystemService
18+
import com.google.android.gms.location.LocationSettingsStates
19+
import org.microg.gms.location.hasNetworkLocationServiceBuiltIn
20+
21+
data class DetailedLocationSettingsStates(
22+
val gpsSystemFeature: Boolean,
23+
val networkLocationSystemFeature: Boolean,
24+
val bluetoothLeSystemFeature: Boolean,
25+
val gpsProviderEnabled: Boolean,
26+
val networkLocationProviderEnabled: Boolean,
27+
val networkLocationProviderBuiltIn: Boolean,
28+
val fineLocationPermission: Boolean,
29+
val coarseLocationPermission: Boolean,
30+
val backgroundLocationPermission: Boolean,
31+
val blePresent: Boolean,
32+
val bleEnabled: Boolean,
33+
val bleScanAlways: Boolean,
34+
val airplaneMode: Boolean,
35+
) {
36+
val gpsPresent: Boolean
37+
get() = gpsSystemFeature
38+
val networkLocationPresent: Boolean
39+
get() = networkLocationSystemFeature || networkLocationProviderBuiltIn
40+
val gpsUsable: Boolean
41+
get() = gpsProviderEnabled && fineLocationPermission && backgroundLocationPermission
42+
val networkLocationUsable: Boolean
43+
get() = (networkLocationProviderEnabled || networkLocationProviderBuiltIn) && coarseLocationPermission && backgroundLocationPermission
44+
val bleUsable: Boolean
45+
get() = blePresent && (bleEnabled || (bleScanAlways && !airplaneMode))
46+
47+
fun toApi() = LocationSettingsStates(gpsUsable, networkLocationUsable, bleUsable, gpsPresent, networkLocationPresent, blePresent)
48+
}
49+
50+
fun Context.getDetailedLocationSettingsStates(): DetailedLocationSettingsStates {
51+
val bluetoothLeSystemFeature = packageManager.hasSystemFeature(FEATURE_BLUETOOTH_LE)
52+
val locationManager = getSystemService<LocationManager>()
53+
val bluetoothManager = if (bluetoothLeSystemFeature) getSystemService<BluetoothManager>() else null
54+
val bleAdapter = bluetoothManager?.adapter
55+
56+
return DetailedLocationSettingsStates(
57+
gpsSystemFeature = packageManager.hasSystemFeature(FEATURE_LOCATION_GPS),
58+
networkLocationSystemFeature = packageManager.hasSystemFeature(FEATURE_LOCATION_NETWORK),
59+
bluetoothLeSystemFeature = bluetoothLeSystemFeature,
60+
gpsProviderEnabled = locationManager?.isProviderEnabled(GPS_PROVIDER) == true,
61+
networkLocationProviderEnabled = locationManager?.isProviderEnabled(NETWORK_PROVIDER) == true,
62+
networkLocationProviderBuiltIn = hasNetworkLocationServiceBuiltIn(),
63+
fineLocationPermission = packageManager.checkPermission(ACCESS_FINE_LOCATION, packageName) == PERMISSION_GRANTED,
64+
coarseLocationPermission = packageManager.checkPermission(ACCESS_COARSE_LOCATION, packageName) == PERMISSION_GRANTED,
65+
backgroundLocationPermission = if (SDK_INT < 29) true else
66+
packageManager.checkPermission(ACCESS_BACKGROUND_LOCATION, packageName) == PERMISSION_GRANTED,
67+
blePresent = bleAdapter != null,
68+
bleEnabled = bleAdapter?.isEnabled == true,
69+
bleScanAlways = Settings.Global.getInt(contentResolver, "ble_scan_always_enabled", 0) == 1,
70+
airplaneMode = Settings.Global.getInt(contentResolver, Settings.Global.AIRPLANE_MODE_ON, 0) != 0
71+
)
72+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* SPDX-FileCopyrightText: 2023 microG Project Team
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package org.microg.gms.location.settings
7+
8+
import android.app.Activity
9+
import android.content.Context
10+
import android.content.Intent
11+
import android.location.LocationManager
12+
import android.os.Bundle
13+
import android.provider.Settings
14+
import android.util.Log
15+
import android.widget.TextView
16+
import org.microg.gms.location.core.R
17+
18+
const val ACTION_LOCATION_SETTINGS_CHECKER = "com.google.android.gms.location.settings.CHECK_SETTINGS"
19+
20+
private const val REQUEST_CODE_LOCATION = 120
21+
const val EXTRA_ORIGINAL_PACKAGE_NAME = "originalPackageName"
22+
const val EXTRA_SETTINGS_REQUEST = "locationSettingsRequests"
23+
24+
class LocationSettingsCheckerActivity : Activity() {
25+
26+
override fun onCreate(savedInstanceState: Bundle?) {
27+
super.onCreate(savedInstanceState)
28+
setContentView(R.layout.activity_location_setting_checker)
29+
30+
findViewById<TextView>(R.id.location_setting_checker_sure).setOnClickListener {
31+
// TODO: We also should handle permissions, Airplane Mode and BLE here.
32+
val intent = Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS)
33+
startActivityForResult(intent, REQUEST_CODE_LOCATION)
34+
}
35+
findViewById<TextView>(R.id.location_setting_checker_cancel).setOnClickListener {
36+
checkerBack(RESULT_CANCELED)
37+
}
38+
}
39+
40+
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
41+
if (requestCode == REQUEST_CODE_LOCATION && isLocationEnabled(this)) {
42+
checkerBack(RESULT_OK)
43+
} else {
44+
super.onActivityResult(requestCode, resultCode, data)
45+
}
46+
}
47+
48+
override fun onBackPressed() {
49+
checkerBack(RESULT_CANCELED)
50+
}
51+
52+
private fun checkerBack(resultCode: Int) {
53+
setResult(resultCode)
54+
finish()
55+
}
56+
57+
private fun isLocationEnabled(context: Context): Boolean {
58+
val locationManager = context.getSystemService(LOCATION_SERVICE) as LocationManager
59+
return locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER) || locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)
60+
}
61+
62+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<shape xmlns:android="http://schemas.android.com/apk/res/android"
3+
android:shape="rectangle">
4+
<solid android:color="#2a2931" />
5+
<corners android:radius="20dp" />
6+
</shape>
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
3+
xmlns:app="http://schemas.android.com/apk/res-auto"
4+
android:layout_width="match_parent"
5+
android:layout_height="match_parent"
6+
android:orientation="vertical"
7+
android:paddingHorizontal="20dp"
8+
android:paddingTop="16dp">
9+
10+
<TextView
11+
android:layout_width="match_parent"
12+
android:layout_height="wrap_content"
13+
android:text="@string/location_settings_dialog_message_title_to_continue"
14+
android:textColor="#e4e1e5"
15+
android:textSize="16sp"
16+
app:layout_constraintLeft_toLeftOf="parent"
17+
app:layout_constraintRight_toRightOf="parent"
18+
app:layout_constraintTop_toTopOf="parent" />
19+
20+
<TextView
21+
android:layout_width="match_parent"
22+
android:layout_height="wrap_content"
23+
android:layout_marginVertical="10dp"
24+
android:text="@string/location_settings_dialog_message_details_start_paragraph"
25+
android:textColor="#e4e1e5"
26+
android:textSize="13sp"
27+
app:layout_constraintLeft_toLeftOf="parent"
28+
app:layout_constraintRight_toRightOf="parent"
29+
app:layout_constraintTop_toTopOf="parent" />
30+
31+
<TextView
32+
android:layout_width="match_parent"
33+
android:layout_height="wrap_content"
34+
android:layout_marginVertical="8dp"
35+
android:layout_marginStart="30dp"
36+
android:text="@string/location_settings_dialog_message_location_services_gps_and_nlp"
37+
android:textColor="#e4e1e5"
38+
android:textSize="13sp"
39+
app:layout_constraintLeft_toLeftOf="parent"
40+
app:layout_constraintRight_toRightOf="parent"
41+
app:layout_constraintTop_toTopOf="parent" />
42+
43+
<TextView
44+
android:layout_width="match_parent"
45+
android:layout_height="wrap_content"
46+
android:layout_marginVertical="8dp"
47+
android:layout_marginStart="30dp"
48+
android:text="@string/location_settings_dialog_message_gls_consent"
49+
android:textColor="#e4e1e5"
50+
android:textSize="13sp"
51+
app:layout_constraintLeft_toLeftOf="parent"
52+
app:layout_constraintRight_toRightOf="parent"
53+
app:layout_constraintTop_toTopOf="parent" />
54+
55+
<TextView
56+
android:layout_width="match_parent"
57+
android:layout_height="wrap_content"
58+
android:layout_marginVertical="10dp"
59+
android:text="@string/location_settings_dialog_message_details_end_paragraph"
60+
android:textColor="#e4e1e5"
61+
android:textSize="13sp"
62+
app:layout_constraintLeft_toLeftOf="parent"
63+
app:layout_constraintRight_toRightOf="parent"
64+
app:layout_constraintTop_toTopOf="parent" />
65+
66+
<LinearLayout
67+
android:layout_width="match_parent"
68+
android:layout_height="wrap_content"
69+
android:layout_marginTop="30dp"
70+
android:layout_marginEnd="10dp"
71+
android:layout_marginBottom="24dp"
72+
android:gravity="right"
73+
android:orientation="horizontal">
74+
75+
<TextView
76+
android:id="@+id/location_setting_checker_cancel"
77+
android:layout_width="wrap_content"
78+
android:layout_height="wrap_content"
79+
android:text="@string/location_settings_dialog_btn_cancel"
80+
android:textColor="#c0c1fa"
81+
android:textSize="13sp"
82+
app:layout_constraintLeft_toLeftOf="parent"
83+
app:layout_constraintRight_toRightOf="parent"
84+
app:layout_constraintTop_toTopOf="parent" />
85+
86+
<TextView
87+
android:id="@+id/location_setting_checker_sure"
88+
android:layout_width="wrap_content"
89+
android:layout_height="wrap_content"
90+
android:layout_marginLeft="30dp"
91+
android:text="@string/location_settings_dialog_btn_sure"
92+
android:textColor="#c0c1fa"
93+
android:textSize="13sp"
94+
app:layout_constraintLeft_toLeftOf="parent"
95+
app:layout_constraintRight_toRightOf="parent"
96+
app:layout_constraintTop_toTopOf="parent" />
97+
98+
</LinearLayout>
99+
100+
</LinearLayout>

0 commit comments

Comments
 (0)