Skip to content

Commit

Permalink
Location: Ask for permission via notification
Browse files Browse the repository at this point in the history
This feature is for Huawei flavors only for now
  • Loading branch information
mar-v-in committed Dec 12, 2023
1 parent 7de8124 commit b93843d
Show file tree
Hide file tree
Showing 14 changed files with 535 additions and 8 deletions.
19 changes: 12 additions & 7 deletions play-services-location/core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,8 @@ android {
minSdkVersion androidMinSdk
targetSdkVersion androidTargetSdk
buildConfigField "String", "ICHNAEA_KEY", "\"${localProperties.get("ichnaea.key", "")}\""
}

sourceSets {
main.java.srcDirs += 'src/main/kotlin'
buildConfigField "String", "FORCE_SHOW_BACKGROUND_PERMISSION", "\"\""
buildConfigField "boolean", "SHOW_NOTIFICATION_WHEN_NOT_PERMITTED", "false"
}

lintOptions {
Expand All @@ -54,19 +52,26 @@ android {
flavorDimensions = ['target']
productFlavors {
"default" {
buildConfigField "String", "FORCE_SHOW_BACKGROUND_PERMISSION", "\"\""
dimension 'target'
}
"huawei" {
buildConfigField "String", "FORCE_SHOW_BACKGROUND_PERMISSION", "\"\""
dimension 'target'
buildConfigField "boolean", "SHOW_NOTIFICATION_WHEN_NOT_PERMITTED", "true"
}
"huaweilh" {
buildConfigField "String", "FORCE_SHOW_BACKGROUND_PERMISSION", "\"com.huawei.permission.sec.MDM.v2\""
dimension 'target'
buildConfigField "String", "FORCE_SHOW_BACKGROUND_PERMISSION", "\"com.huawei.permission.sec.MDM.v2\""
buildConfigField "boolean", "SHOW_NOTIFICATION_WHEN_NOT_PERMITTED", "true"
}
}

sourceSets {
main.java.srcDirs += 'src/main/kotlin'
huawei.java.srcDirs += 'src/huawei/kotlin'
huaweilh.java.srcDirs += huawei.java.srcDirs
huaweilh.res.srcDirs += huawei.res.srcDirs
}

compileOptions {
sourceCompatibility = 1.8
targetCompatibility = 1.8
Expand Down
34 changes: 34 additions & 0 deletions play-services-location/core/src/huawei/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ SPDX-FileCopyrightText: 2020 microG Project Team
~ SPDX-License-Identifier: Apache-2.0
-->
<manifest xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android">

<application>
<activity
android:name="org.microg.gms.location.manager.AskPermissionNotificationActivity"
android:excludeFromRecents="true"
android:process=":ui"
android:theme="@style/Theme.AppCompat.DayNight.NoActionBar"
android:exported="false" />

<service
android:name="org.microg.gms.location.manager.LocationManagerService"
android:exported="true">
<intent-filter>
<action android:name="com.google.android.location.internal.GoogleLocationManagerService.START" />
</intent-filter>
</service>
<service
android:name="org.microg.gms.location.reporting.ReportingAndroidService"
android:exported="true">
<intent-filter>
<action android:name="com.google.android.location.reporting.service.START" />
<action android:name="com.google.android.gms.location.reporting.service.START" />

<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</service>
</application>
</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,308 @@
/*
* SPDX-FileCopyrightText: 2023 microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/

package org.microg.gms.location.manager

import android.Manifest
import android.Manifest.permission.*
import android.annotation.TargetApi
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.app.PendingIntent.FLAG_IMMUTABLE
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.pm.PackageManager
import android.graphics.Color
import android.graphics.Typeface
import android.net.Uri
import android.os.Build.VERSION.SDK_INT
import android.os.Bundle
import android.provider.Settings
import android.text.Spannable
import android.text.SpannableStringBuilder
import android.text.style.ForegroundColorSpan
import android.text.style.StyleSpan
import android.util.Log
import android.view.View
import android.widget.TextView
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat
import androidx.core.content.ContextCompat.RECEIVER_NOT_EXPORTED
import androidx.core.content.getSystemService
import org.microg.gms.location.core.R
import org.microg.gms.location.core.BuildConfig
import org.microg.gms.utils.getApplicationLabel

@RequiresApi(23)
class AskPermissionNotificationActivity : AppCompatActivity() {

private val foregroundRequestCode = 5
private val backgroundRequestCode = 55
private val sharedPreferences by lazy {
getSharedPreferences(SHARED_PREFERENCE_NAME, MODE_PRIVATE)
}
private lateinit var hintView: View

private lateinit var rationaleTextView: TextView

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.extended_permission_request)
rationaleTextView = findViewById(R.id.rationale_textview)

if (checkAllPermissions()) {
hideLocationPermissionNotification(this)
finish()
} else if (isGranted(ACCESS_COARSE_LOCATION) && isGranted(ACCESS_FINE_LOCATION) && !isGranted(ACCESS_BACKGROUND_LOCATION) && SDK_INT >= 29) {
requestBackground()
} else {
requestForeground()
}


findViewById<View>(R.id.open_setting_tv).setOnClickListener {
val intent = Intent()
intent.action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS
val uri = Uri.fromParts("package", packageName, null)
intent.data = uri
startActivityForResult(intent, 123)
}

findViewById<View>(R.id.decline_remind_tv).setOnClickListener {
val editor = sharedPreferences.edit()
editor.putBoolean(PERMISSION_REJECT_SHOW, true)
editor.apply()
finish()
}

hintView = findViewById(R.id.hint_sl)

val hintTitle = getString(R.string.permission_hint_title)
val builder = SpannableStringBuilder(hintTitle + getString(R.string.permission_hint))
val span = ForegroundColorSpan(Color.BLACK)
builder.setSpan(span, 0, hintTitle.length, Spannable.SPAN_INCLUSIVE_EXCLUSIVE)
builder.setSpan(StyleSpan(Typeface.BOLD), 0, hintTitle.length, Spannable.SPAN_INCLUSIVE_EXCLUSIVE)

val hintContentTv = findViewById<TextView>(R.id.hint_content_tv)
hintContentTv.text = builder

val showTimes = sharedPreferences.getInt(PERMISSION_SHOW_TIMES, 0)
Log.d(TAG, "reject show times:$showTimes")
if (showTimes >= 1) {
hintView.visibility = View.VISIBLE
}
}

private fun checkAllPermissions(): Boolean {
if (SDK_INT < 23) return true
return if (SDK_INT >= 29) {
isGranted(ACCESS_COARSE_LOCATION)
&& isGranted(ACCESS_FINE_LOCATION)
&& isGranted(ACCESS_BACKGROUND_LOCATION)
} else {
isGranted(ACCESS_COARSE_LOCATION)
&& isGranted(ACCESS_FINE_LOCATION)
}
}

private fun isGranted(permission: String): Boolean {
return checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED
}

private fun checkAndAddPermission(list: ArrayList<String>, permission: String) {
val result = checkSelfPermission(permission)
Log.i(TAG, "$permission: $result")
if (result != PackageManager.PERMISSION_GRANTED) {
list.add(permission)
}
}

private fun requestForeground() {
val appName = packageManager.getApplicationLabel(packageName)
rationaleTextView.text = getString(R.string.rationale_foreground_permission, appName)
val permissions = arrayListOf<String>()

if (BuildConfig.FORCE_SHOW_BACKGROUND_PERMISSION.isNotEmpty()) {
permissions.add(BuildConfig.FORCE_SHOW_BACKGROUND_PERMISSION)
}
checkAndAddPermission(permissions, ACCESS_COARSE_LOCATION)
checkAndAddPermission(permissions, ACCESS_FINE_LOCATION)
if (SDK_INT == 29) {
rationaleTextView.text = getString(R.string.rationale_permission, appName)
checkAndAddPermission(permissions, ACCESS_BACKGROUND_LOCATION)
}
requestPermissions(permissions, foregroundRequestCode)
}

private fun requestBackground() {
rationaleTextView.setText(R.string.rationale_background_permission)
val permissions = arrayListOf<String>()
if (BuildConfig.FORCE_SHOW_BACKGROUND_PERMISSION.isNotEmpty()) {
permissions.add(BuildConfig.FORCE_SHOW_BACKGROUND_PERMISSION)
}
if (SDK_INT >= 29) {
checkAndAddPermission(permissions, ACCESS_BACKGROUND_LOCATION)
}
requestPermissions(permissions, backgroundRequestCode)
}

private fun requestPermissions(permissions: ArrayList<String>, requestCode: Int) {
if (permissions.isNotEmpty()) {
Log.w(TAG, "Request permissions: $permissions")
ActivityCompat.requestPermissions(this, permissions.toTypedArray(), requestCode)
} else {
Log.i(TAG, "All permission granted")
setResult(RESULT_OK)
finish()
}
}

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
Log.d(TAG, "onActivityResult: ")
checkPermissions()
}

private fun checkPermissions() {
val permissions = mutableListOf(ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION)
if (SDK_INT >= 29) permissions.add(ACCESS_BACKGROUND_LOCATION)

if (permissions.all { checkSelfPermission(it) == PackageManager.PERMISSION_GRANTED }) {
Log.d(TAG, "location permission is all granted")
hideLocationPermissionNotification(this)
finish()
}
}

override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
when (requestCode) {
foregroundRequestCode -> {
for (i in permissions.indices) {
val p = permissions[i]
val grant = grantResults[i]
val msg = if (grant == PackageManager.PERMISSION_GRANTED) "GRANTED" else "DENIED"
Log.w(TAG, "$p: $grant - $msg")
}
requestBackground()
}

backgroundRequestCode -> {
if (isGranted(ACCESS_BACKGROUND_LOCATION)) {
hideLocationPermissionNotification(this)
setResult(RESULT_OK)
finish()
} else {
reject()
}
}

else -> {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
reject()
}
}
}

private fun reject() {
val showTimes = sharedPreferences.getInt(PERMISSION_SHOW_TIMES, 0)
Log.d(TAG, "reject show times:$showTimes")
if (showTimes >= 1) {
hintView.visibility = View.VISIBLE
} else {
val editor = sharedPreferences.edit()
editor.putInt(PERMISSION_SHOW_TIMES, showTimes + 1)
editor.apply()
setResult(RESULT_CANCELED)
finish()
}

}


companion object {
private const val SHARED_PREFERENCE_NAME = "location_perm_notify"
const val PERMISSION_SHOW_TIMES = "permission_show_times"
const val PERMISSION_REJECT_SHOW = "permission_reject_show"
private const val NOTIFICATION_ID = 1026359765

private var notificationIsShown = false

@JvmStatic
fun showLocationPermissionNotification(context: Context) {
if (notificationIsShown) return
AskPermissionNotificationCancel.register(context)
val appName = context.packageManager.getApplicationLabel(context.packageName).toString()
val title = context.getString(R.string.location_permission_notification_title, appName)
val backgroundPermissionOption =
if (SDK_INT >= 30) context.packageManager.backgroundPermissionOptionLabel else context.getString(R.string.location_permission_background_option_name)
val text = context.getString(R.string.location_permission_notification_content, backgroundPermissionOption, appName)
val notification = NotificationCompat.Builder(context, createNotificationChannel(context))
.setContentTitle(title).setContentText(text)
.setSmallIcon(R.drawable.ic_permission_notification)
.setContentIntent(PendingIntent.getActivity(context, 0, Intent(context, AskPermissionNotificationActivity::class.java), FLAG_IMMUTABLE))
.setStyle(NotificationCompat.BigTextStyle().bigText(text))
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setOngoing(true)
.setDeleteIntent(PendingIntent.getBroadcast(context, 0, AskPermissionNotificationCancel.getTrigger(context), FLAG_IMMUTABLE))
.build()
context.getSystemService<NotificationManager>()?.notify(NOTIFICATION_ID, notification)
notificationIsShown = true
}

@JvmStatic
fun hideLocationPermissionNotification(context: Context) {
if (!notificationIsShown) return
context.getSystemService<NotificationManager>()?.cancel(NOTIFICATION_ID)
AskPermissionNotificationCancel.unregister(context)
notificationIsShown = false
}

@TargetApi(26)
private fun createNotificationChannel(context: Context): String {
val channelId = "missing-location-permission"
if (SDK_INT >= 26) {
val channel = NotificationChannel(channelId, "Missing location permission", NotificationManager.IMPORTANCE_HIGH)
channel.setSound(null, null)
channel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC
channel.setShowBadge(true)
if (SDK_INT >= 29) {
channel.setAllowBubbles(false)
}
channel.vibrationPattern = longArrayOf(0)
context.getSystemService<NotificationManager>()?.createNotificationChannel(channel)
return channel.id
}
return channelId
}

}
}

@RequiresApi(23)
private object AskPermissionNotificationCancel : BroadcastReceiver() {
private const val ACTION = "org.microg.gms.location.manager.ASK_PERMISSION_CANCEL"
override fun onReceive(context: Context, intent: Intent?) {
AskPermissionNotificationActivity.hideLocationPermissionNotification(context)
}

fun getTrigger(context: Context): Intent {
return Intent(ACTION).apply { `package` = context.packageName }
}

fun register(context: Context) {
ContextCompat.registerReceiver(context, this, IntentFilter(ACTION), RECEIVER_NOT_EXPORTED)
}

fun unregister(context: Context) {
context.unregisterReceiver(this)
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M1,21h22L12,2 1,21zM13,18h-2v-2h2v2zM13,14h-2v-4h2v4z"/>
</vector>
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit b93843d

Please sign in to comment.