Skip to content

Commit

Permalink
Merge branch 'main' into gradle-versions-check
Browse files Browse the repository at this point in the history
  • Loading branch information
pcolby committed Jun 22, 2024
2 parents f3836ff + 7b9fac3 commit 3aed3d8
Show file tree
Hide file tree
Showing 93 changed files with 250 additions and 37 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
.envrc
.gradle
.idea
.kotlin
build
debug
local.properties
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## [Unreleased][]

Replaced timer with broadcast listener for more responsive tile updates.

Added support for direct NFC toggle if granted `WRITE_SECURE_SETTINGS` permission.

## [1.3.1][] (2023-12-04)

Enabled code minification and resource shrinkage.
Expand Down
6 changes: 3 additions & 3 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,9 @@ base {
}

dependencies {
implementation 'androidx.core:core-ktx:1.12.0'
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.11.0'
implementation 'androidx.core:core-ktx:1.13.1'
implementation 'androidx.appcompat:appcompat:1.7.0'
implementation 'com.google.android.material:material:1.12.0'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
Expand Down
4 changes: 4 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

<uses-feature android:name="android.hardware.nfc" android:required="true" />

<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" tools:ignore="ProtectedPermissions" />

<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
Expand All @@ -24,6 +26,8 @@
<intent-filter>
<action android:name="android.service.quicksettings.action.QS_TILE" />
</intent-filter>
<meta-data android:name="android.service.quicksettings.TOGGLEABLE_TILE"
android:value="true" />
</service>
</application>
</manifest>
176 changes: 147 additions & 29 deletions app/src/main/java/au/id/colby/nfcquicksettings/NfcTileService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,24 @@

package au.id.colby.nfcquicksettings

import android.Manifest.permission.WRITE_SECURE_SETTINGS
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.PERMISSION_GRANTED
import android.nfc.NfcAdapter
import android.os.Build
import android.os.Build.VERSION.SDK_INT
import android.os.Build.VERSION_CODES
import android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE
import android.provider.Settings
import android.service.quicksettings.Tile
import android.service.quicksettings.TileService
import android.util.Log
import androidx.core.content.ContextCompat
import au.id.colby.nfcquicksettings.R.string
import java.util.Timer
import kotlin.concurrent.fixedRateTimer

private const val TAG = "NfcTileService"

Expand All @@ -27,60 +31,174 @@ private const val TAG = "NfcTileService"
* device's NFC Settings activity.
*/
class NfcTileService : TileService() {
private var updateTimer: Timer? = null
private val nfcBroadcastReceiver = NfcBroadcastReceiver()

/**
* Called when this tile moves into a listening state.
*
* This override updates the tile's state and title to indicate the device's current NFC status
* (On, Off, or Unavailable).
* This override registers a broadcast receiver to listen for NFC adapter state changes, then
* updates the tile according the default adapter's current state.
*/
override fun onStartListening() {
super.onStartListening()
Log.d(TAG, "onStartListening")
val adapter: NfcAdapter? = NfcAdapter.getDefaultAdapter(this)
updateTimer = fixedRateTimer("default", false, 0L, 500) {
Log.d(TAG, "updateTimer")
qsTile?.apply {
Log.d(TAG, "Updating tile")
state = if (adapter == null) Tile.STATE_INACTIVE else
if (adapter.isEnabled) Tile.STATE_ACTIVE else Tile.STATE_INACTIVE
if (SDK_INT >= Build.VERSION_CODES.Q) subtitle = getText(
if (adapter == null) string.tile_subtitle_unavailable else
if (adapter.isEnabled) string.tile_subtitle_active else string.tile_subtitle_inactive
)
updateTile()
}
}
Log.d(TAG, "onStartListening; Registering broadcast receiver")
ContextCompat.registerReceiver(
this,
nfcBroadcastReceiver,
IntentFilter(NfcAdapter.ACTION_ADAPTER_STATE_CHANGED),
ContextCompat.RECEIVER_EXPORTED
)
updateTile()
}

/**
* Called when this tile moves out of the listening state.
*
* This override cancels the update timer, if any is running.
* This override simply unregisters the broadcast receiver.
*/
override fun onStopListening() {
Log.d(TAG, "onStopListening")
updateTimer?.apply {
Log.d(TAG, "Cancelling update timer")
cancel()
}
Log.d(TAG, "onStopListening; Unregistering broadcast receiver")
unregisterReceiver(nfcBroadcastReceiver)
super.onStopListening()
}

/**
* Called when the user clicks on this tile.
*
* This override takes the user to the NFC settings, by starting the `ACTION_NFC_SETTINGS`
* activity, and collapsing the Quick Settings menu.
* This override attempts to invert the default NFC adapter's state, and if that cannot be done,
* launches the NFC Settings Action, where the user can toggle it themselves.
*/
override fun onClick() {
super.onClick()
Log.d(TAG, "onClick")
if (!invertNfcState()) startNfcSettingsActivity()
}

/**
* Inverts the NFC [adapter]'s current state.
*
* That is, if [adapter] is currently enabled, then disable it, and vice versa. This calls
* [setNfcAdapterState], which in turn, requires WRITE_SECURE_SETTINGS permission.
*
* @return true if the [adapter]'s new state was successfully requested.
*/
private fun invertNfcState(adapter: NfcAdapter? = NfcAdapter.getDefaultAdapter(this)): Boolean {
return adapter?.run { setNfcAdapterState(this, !isEnabled) } ?: false
}

/**
* Checks if [permission] has been granted to us.
*
* @return true if we have [permission], otherwise false.
*/
private fun permissionGranted(permission: String): Boolean {
val status = checkSelfPermission(permission)
Log.d(TAG, "$permission status: $status")
return status == PERMISSION_GRANTED
}

/**
* Sets the [adapter] state to [enable].
*
* This uses introspection to execute either the NfcAdapter::enable() or NfcAdapter::disable()
* function as appropriate. These functions both require WRITE_SECURE_SETTINGS permission.
*
* @return true if the state change was successfully requested, otherwise false.
*/
private fun setNfcAdapterState(adapter: NfcAdapter, enable: Boolean): Boolean {
if (!permissionGranted(WRITE_SECURE_SETTINGS)) return false
val methodName = if (enable) "enable" else "disable"
Log.i(TAG, "Setting NFC adapter's status to ${methodName}d")
if (enable) updateTile(Tile.STATE_ACTIVE, string.tile_subtitle_turning_on)
val success = try {
Log.d(TAG, "Invoking NfcAdapter::$methodName()")
val result = NfcAdapter::class.java.getMethod(methodName).invoke(adapter)
Log.d(TAG, "NfcAdapter::$methodName() returned $result")
result is Boolean && result
} catch (e: Exception) {
Log.e(TAG, "Failed to invoke NfcAdapter::$methodName()", e)
false
}
if (enable && !success) updateTile() // Clear the 'Turning on...' state.
if (!enable && success) updateTile(false) // Show the tile as inactive already.
return success
}

/**
* Starts the NFC Settings activity, then collapses the Quick Settings panel behind it.
*/
private fun startNfcSettingsActivity() {
Log.i(TAG, "Starting the ACTION_NFC_SETTINGS activity")
val intent = Intent(Settings.ACTION_NFC_SETTINGS)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
//noinspection StartActivityAndCollapseDeprecated
if (SDK_INT < UPSIDE_DOWN_CAKE) @Suppress("DEPRECATION") startActivityAndCollapse(intent)
else startActivityAndCollapse(PendingIntent.getActivity(this, 0, intent, FLAG_IMMUTABLE))
}

/**
* Updates the Quick Settings tile with the [newState] and (if supported) [newSubTitleResId].
*
* Note [newSubTitleResId] will be ignored on devices running Android versions earlier than Q.
*
* @param newState The next state for the tile. Should be one of the Tile.STATE_* constants.
* @param newSubTitleResId Resource ID for the new subtitle text.
*/
private fun updateTile(newState: Int, newSubTitleResId: Int) {
qsTile?.apply {
Log.d(TAG, "Updating tile with state $newState")
this.state = newState
if (SDK_INT >= VERSION_CODES.Q) this.subtitle = getText(newSubTitleResId)
updateTile()
}
}

/**
* Updates the Quick Settings tile to show as active or not.
*
* @param active If true show the tile as active, otherwise show as inactive.
*/
private fun updateTile(active: Boolean) {
if (active) updateTile(Tile.STATE_ACTIVE, string.tile_subtitle_active)
else updateTile(Tile.STATE_INACTIVE, string.tile_subtitle_inactive)
}

/**
* Updates the Quick Settings tile to reflect the [adapter]'s current state.
*
* @param adapter The adapter to reflect the state of.
*/
private fun updateTile(adapter: NfcAdapter? = NfcAdapter.getDefaultAdapter(this)) {
adapter?.apply { updateTile(isEnabled) } ?:
updateTile(Tile.STATE_INACTIVE, string.tile_subtitle_unavailable)
}

/**
* Provides static functions for the NfcTileService class.
*/
companion object {
/**
* Updates the Quick Settings tile owned by [context], which is an NfcTileService instance.
*/
fun updateTile(context: Context) {
(context as? NfcTileService)?.run { updateTile(); }
}
}

/**
* A custom Broadcast Receiver for updating an NfcTileService on NFC adapter state changes.
*/
inner class NfcBroadcastReceiver : BroadcastReceiver() {
/**
* Called when a broadcast message is received.
*
* This override simply reflects the event back to the [context] for which it the
* receiver was registered.
*/
override fun onReceive(context: Context, intent: Intent) {
Log.d(TAG, "onReceive $context $intent")
if (intent.action == NfcAdapter.ACTION_ADAPTER_STATE_CHANGED) updateTile(context)
else Log.w(TAG, "Received unexpected broadcast message: $intent")
}
}
}
1 change: 1 addition & 0 deletions app/src/main/res/values-af/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
<resources>
<string name="tile_subtitle_active">Aan</string>
<string name="tile_subtitle_inactive">Af</string>
<string name="tile_subtitle_turning_on">Skakel tans aan …</string>
<string name="tile_subtitle_unavailable">Onbeskikbaar</string>
</resources>
1 change: 1 addition & 0 deletions app/src/main/res/values-am/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
<resources>
<string name="tile_subtitle_active">በርቷል</string>
<string name="tile_subtitle_inactive">ጠፍቷል</string>
<string name="tile_subtitle_turning_on">በማብራት ላይ...</string>
<string name="tile_subtitle_unavailable">አይገኝም</string>
</resources>
1 change: 1 addition & 0 deletions app/src/main/res/values-ar/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
<resources>
<string name="tile_subtitle_active">الميزة مفعّلة</string>
<string name="tile_subtitle_inactive">الميزة غير مفعّلة</string>
<string name="tile_subtitle_turning_on">جارٍ التفعيل…</string>
<string name="tile_subtitle_unavailable">الميزة غير متاحة</string>
</resources>
1 change: 1 addition & 0 deletions app/src/main/res/values-as/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
<resources>
<string name="tile_subtitle_active">অন কৰা আছে</string>
<string name="tile_subtitle_inactive">অফ আছে</string>
<string name="tile_subtitle_turning_on">অন কৰি থকা হৈছে…</string>
<string name="tile_subtitle_unavailable">উপলব্ধ নহয়</string>
</resources>
1 change: 1 addition & 0 deletions app/src/main/res/values-az/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
<resources>
<string name="tile_subtitle_active">Aktiv</string>
<string name="tile_subtitle_inactive">Deaktiv</string>
<string name="tile_subtitle_turning_on">Aktiv edilir...</string>
<string name="tile_subtitle_unavailable">Əlçatan deyil</string>
</resources>
1 change: 1 addition & 0 deletions app/src/main/res/values-b+sr+Latn/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
<resources>
<string name="tile_subtitle_active">Uključeno</string>
<string name="tile_subtitle_inactive">Isključeno</string>
<string name="tile_subtitle_turning_on">Uključuje se...</string>
<string name="tile_subtitle_unavailable">Nedostupno</string>
</resources>
1 change: 1 addition & 0 deletions app/src/main/res/values-be/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
<resources>
<string name="tile_subtitle_active">Уключана</string>
<string name="tile_subtitle_inactive">Выключана</string>
<string name="tile_subtitle_turning_on">Уключэнне…</string>
<string name="tile_subtitle_unavailable">Недаступна</string>
</resources>
1 change: 1 addition & 0 deletions app/src/main/res/values-bg/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
<resources>
<string name="tile_subtitle_active">Вкл.</string>
<string name="tile_subtitle_inactive">Изкл.</string>
<string name="tile_subtitle_turning_on">Включва се...</string>
<string name="tile_subtitle_unavailable">Не е налице</string>
</resources>
1 change: 1 addition & 0 deletions app/src/main/res/values-bn/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
<resources>
<string name="tile_subtitle_active">চালু আছে</string>
<string name="tile_subtitle_inactive">বন্ধ আছে</string>
<string name="tile_subtitle_turning_on">চালু করা হচ্ছে…</string>
<string name="tile_subtitle_unavailable">উপলভ্য নেই</string>
</resources>
1 change: 1 addition & 0 deletions app/src/main/res/values-bs/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
<resources>
<string name="tile_subtitle_active">Uključeno</string>
<string name="tile_subtitle_inactive">Isključeno</string>
<string name="tile_subtitle_turning_on">Uključivanje…</string>
<string name="tile_subtitle_unavailable">Nedostupno</string>
</resources>
1 change: 1 addition & 0 deletions app/src/main/res/values-ca/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
<resources>
<string name="tile_subtitle_active">Activat</string>
<string name="tile_subtitle_inactive">Desactivat</string>
<string name="tile_subtitle_turning_on">S\'està activant…</string>
<string name="tile_subtitle_unavailable">No disponible</string>
</resources>
1 change: 1 addition & 0 deletions app/src/main/res/values-cs/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
<resources>
<string name="tile_subtitle_active">Zapnuto</string>
<string name="tile_subtitle_inactive">Vypnuto</string>
<string name="tile_subtitle_turning_on">Zapínání…</string>
<string name="tile_subtitle_unavailable">Nedostupné</string>
</resources>
1 change: 1 addition & 0 deletions app/src/main/res/values-da/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
<resources>
<string name="tile_subtitle_active">Til</string>
<string name="tile_subtitle_inactive">Fra</string>
<string name="tile_subtitle_turning_on">Aktiverer…</string>
<string name="tile_subtitle_unavailable">Ikke tilgængelig</string>
</resources>
1 change: 1 addition & 0 deletions app/src/main/res/values-de/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
<resources>
<string name="tile_subtitle_active">An</string>
<string name="tile_subtitle_inactive">Aus</string>
<string name="tile_subtitle_turning_on">Wird aktiviert…</string>
<string name="tile_subtitle_unavailable">Nicht verfügbar</string>
</resources>
1 change: 1 addition & 0 deletions app/src/main/res/values-el/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
<resources>
<string name="tile_subtitle_active">Ενεργό</string>
<string name="tile_subtitle_inactive">Ανενεργό</string>
<string name="tile_subtitle_turning_on">Ενεργοποίηση…</string>
<string name="tile_subtitle_unavailable">Μη διαθέσιμο</string>
</resources>
1 change: 1 addition & 0 deletions app/src/main/res/values-en-rAU/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
<resources>
<string name="tile_subtitle_active">On</string>
<string name="tile_subtitle_inactive">Off</string>
<string name="tile_subtitle_turning_on">Turning on…</string>
<string name="tile_subtitle_unavailable">Unavailable</string>
</resources>
1 change: 1 addition & 0 deletions app/src/main/res/values-en-rCA/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
<resources>
<string name="tile_subtitle_active">On</string>
<string name="tile_subtitle_inactive">Off</string>
<string name="tile_subtitle_turning_on">Turning on…</string>
<string name="tile_subtitle_unavailable">Unavailable</string>
</resources>
Loading

0 comments on commit 3aed3d8

Please sign in to comment.