Skip to content

Commit d36b163

Browse files
committed
Support Android 31 API level
1 parent 1bad745 commit d36b163

12 files changed

Lines changed: 129 additions & 42 deletions

File tree

app/build.gradle

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -115,20 +115,21 @@ android {
115115
dependencies {
116116
implementation fileTree(dir: 'libs', include: ['*.jar'])
117117
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
118-
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2'
118+
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.0'
119119
implementation 'com.artemchep.config:config:2.2.0'
120120
implementation 'com.afollestad.material-dialogs:core:3.3.0'
121-
implementation 'com.google.android.material:material:1.6.0-alpha01'
121+
implementation 'com.google.android.material:material:1.6.0-alpha02'
122122
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
123123
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
124+
implementation "androidx.lifecycle:lifecycle-process:$lifecycle_version"
124125
implementation 'com.eightbitlab:blurview:1.6.6'
125126
implementation "ch.acra:acra-http:5.8.4"
126-
implementation 'androidx.appcompat:appcompat:1.3.1'
127+
implementation 'androidx.appcompat:appcompat:1.4.1'
127128
implementation 'androidx.core:core-ktx:1.7.0'
128129
implementation 'androidx.browser:browser:1.4.0'
129-
implementation 'androidx.constraintlayout:constraintlayout:2.1.1'
130-
implementation "androidx.work:work-runtime-ktx:2.7.0"
131-
implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0'
130+
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
131+
implementation "androidx.work:work-runtime-ktx:2.7.1"
132+
implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.1.0'
132133
implementation 'com.mikepenz:fastadapter:3.3.1'
133134
compileOnly 'org.solovyev.android:checkout:1.2.3'
134135
playstoreImplementation 'org.solovyev.android:checkout:1.2.3'

app/src/main/AndroidManifest.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@
3939
android:exported="true">
4040
<intent-filter>
4141
<action android:name="android.intent.action.BOOT_COMPLETED" />
42+
<action android:name="android.intent.action.LOCKED_BOOT_COMPLETED" />
43+
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
4244
</intent-filter>
4345
</receiver>
4446

app/src/main/java/com/artemchep/pocketmode/Heart.kt

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,24 @@ import android.app.Application
55
import android.content.Context
66
import android.content.Intent
77
import android.os.Bundle
8+
import androidx.lifecycle.Lifecycle
9+
import androidx.lifecycle.ProcessLifecycleOwner
10+
import androidx.work.*
811
import com.artemchep.config.Config
912
import com.artemchep.pocketmode.analytics.Analytics
1013
import com.artemchep.pocketmode.analytics.AnalyticsHolder
1114
import com.artemchep.pocketmode.analytics.AnalyticsStub
1215
import com.artemchep.pocketmode.analytics.createAnalytics
1316
import com.artemchep.pocketmode.services.PocketService
17+
import com.artemchep.pocketmode.services.PocketServiceRestartWorker
1418
import com.google.android.material.color.DynamicColors
1519
import org.acra.ACRA
1620
import org.acra.annotation.AcraCore
1721
import org.acra.annotation.AcraHttpSender
1822
import org.acra.data.StringFormat
1923
import org.acra.sender.HttpSender
2024
import org.solovyev.android.checkout.Billing
25+
import java.time.Duration
2126

2227
/**
2328
* @author Artem Chepurnoy
@@ -33,14 +38,20 @@ import org.solovyev.android.checkout.Billing
3338
httpMethod = HttpSender.Method.POST,
3439
)
3540
class Heart : Application() {
41+
companion object {
42+
private const val WORK_RESTART_ID = "PocketService::restart"
43+
private const val WORK_RESTART_PERIOD = 2 * 60 * 60 * 1000L // 2h
44+
}
45+
3646
private val cfgObserver = object : Config.OnConfigChangedListener<String> {
3747
override fun onConfigChanged(keys: Set<String>) {
3848
if (Cfg.KEY_ENABLED in keys) {
3949
// Start or stop the service depending on the
4050
// config.
4151
val shouldBeEnabled = Cfg.isEnabled
4252
if (shouldBeEnabled) {
43-
startForegroundService(pocketServiceIntent)
53+
if (ProcessLifecycleOwner.get().lifecycle.currentState >= Lifecycle.State.STARTED)
54+
startForegroundService(pocketServiceIntent)
4455
}
4556
// Else service should monitor the config and
4657
// kill itself.
@@ -106,9 +117,13 @@ class Heart : Application() {
106117
}
107118
})
108119

109-
// Start the service.
110-
if (Cfg.isEnabled) {
111-
startForegroundService(pocketServiceIntent)
112-
}
120+
// Start a scheduler to restart service in
121+
// few hours in cause it was killed.
122+
val request = PeriodicWorkRequestBuilder<PocketServiceRestartWorker>(
123+
Duration.ofMillis(WORK_RESTART_PERIOD)
124+
).build()
125+
WorkManager
126+
.getInstance(this)
127+
.enqueueUniquePeriodicWork(WORK_RESTART_ID, ExistingPeriodicWorkPolicy.REPLACE, request)
113128
}
114129
}

app/src/main/java/com/artemchep/pocketmode/receivers/BootReceiver.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,15 @@ import com.artemchep.pocketmode.Heart
1111
*/
1212
class BootReceiver : BroadcastReceiver() {
1313
override fun onReceive(context: Context, intent: Intent) {
14-
if (intent.action != Intent.ACTION_BOOT_COMPLETED) {
14+
if (intent.action != Intent.ACTION_BOOT_COMPLETED &&
15+
intent.action != Intent.ACTION_LOCKED_BOOT_COMPLETED &&
16+
intent.action != Intent.ACTION_MY_PACKAGE_REPLACED
17+
) {
1518
return
1619
}
1720

21+
// We can start foreground services at this point, see
22+
// https://developer.android.com/guide/components/foreground-services#background-start-restriction-exemptions
1823
if (Cfg.isEnabled) {
1924
val heart = context.applicationContext as Heart
2025
val pocketServiceIntent = heart.pocketServiceIntent

app/src/main/java/com/artemchep/pocketmode/sensors/FlowOfLockScreen.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ fun flowOfLockScreen(
6161
is Proximity.Far -> flowOf(Idle)
6262
// When the sensor gets covered, wait for a bit and if it
6363
// does not get uncovered -> send the lock screen event.
64-
is Proximity.Near -> flow {
64+
is Proximity.Near -> flow<LockScreenEvent> {
6565
emit(BeforeLockScreen)
6666
delay(Cfg.lockScreenDelay)
6767
emit(OnLockScreen)

app/src/main/java/com/artemchep/pocketmode/services/PocketService.kt

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,10 @@ import kotlin.coroutines.CoroutineContext
3737
*/
3838
class PocketService : Service(), CoroutineScope {
3939
companion object {
40-
private const val WORK_RESTART_ID = "PocketService::restart"
41-
private const val WORK_RESTART_PERIOD = 60 * 60 * 1000L // ms
42-
4340
private const val NOTIFICATION_CHANNEL = "pocket_notification_channel"
4441
private const val NOTIFICATION_ID = 112
42+
43+
var running = false
4544
}
4645

4746
private lateinit var job: Job
@@ -128,6 +127,7 @@ class PocketService : Service(), CoroutineScope {
128127

129128
override fun onCreate() {
130129
job = Job()
130+
running = true
131131
super.onCreate()
132132
Cfg.observe(configObserver)
133133
stopSelfIfPocketServiceIsDisabled()
@@ -148,15 +148,6 @@ class PocketService : Service(), CoroutineScope {
148148
overlayObserver.onChanged(it)
149149
}
150150
}
151-
152-
// Start a scheduler to restart service in
153-
// few hours in cause it was killed.
154-
val request = PeriodicWorkRequestBuilder<PocketServiceRestartWorker>(
155-
Duration.ofMillis(WORK_RESTART_PERIOD)
156-
).build()
157-
WorkManager
158-
.getInstance(this)
159-
.enqueueUniquePeriodicWork(WORK_RESTART_ID, ExistingPeriodicWorkPolicy.KEEP, request)
160151
}
161152

162153
private fun stopSelfIfPocketServiceIsDisabled() {
@@ -199,6 +190,7 @@ class PocketService : Service(), CoroutineScope {
199190
super.onDestroy()
200191
job.cancel()
201192
overlayExitJob?.cancel()
193+
running = false
202194
}
203195

204196
override fun onBind(intent: Intent?): IBinder? = null
Lines changed: 79 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,95 @@
11
package com.artemchep.pocketmode.services
22

3+
import android.app.Notification
4+
import android.app.NotificationChannel
5+
import android.app.NotificationManager
6+
import android.app.PendingIntent
37
import android.content.Context
48
import android.content.Intent
5-
import androidx.work.Worker
9+
import androidx.core.app.NotificationCompat
10+
import androidx.core.content.getSystemService
11+
import androidx.lifecycle.asFlow
12+
import androidx.work.CoroutineWorker
613
import androidx.work.WorkerParameters
7-
import com.artemchep.pocketmode.viewmodels.PocketViewModel
14+
import com.artemchep.pocketmode.Cfg
15+
import com.artemchep.pocketmode.R
16+
import com.artemchep.pocketmode.sensors.AccessAccessibilityLiveData
17+
import com.artemchep.pocketmode.ui.activities.MainActivity
18+
import kotlinx.coroutines.flow.first
819

920
/**
1021
* @author Artem Chepurnoy
1122
*/
1223
class PocketServiceRestartWorker(
1324
context: Context,
1425
workerParams: WorkerParameters
15-
) : Worker(context, workerParams) {
16-
override fun doWork(): Result {
17-
val intent = Intent(applicationContext, PocketViewModel::class.java)
18-
applicationContext.startForegroundService(intent)
26+
) : CoroutineWorker(context, workerParams) {
27+
companion object {
28+
private const val NOTIFICATION_CHANNEL = "pocket_restart_channel"
29+
private const val NOTIFICATION_ID = 212
30+
}
31+
32+
override suspend fun doWork(): Result {
33+
val accessibilityGranted = AccessAccessibilityLiveData(applicationContext)
34+
.asFlow()
35+
.first()
36+
if (Cfg.isEnabled && (!PocketService.running || !accessibilityGranted)) {
37+
val nm = applicationContext.getSystemService<NotificationManager>()!!
38+
val n = createNotification(
39+
accessibilityGranted = accessibilityGranted,
40+
)
41+
nm.notify(NOTIFICATION_ID, n)
42+
}
1943

2044
return Result.success()
2145
}
46+
47+
private fun createNotification(
48+
accessibilityGranted: Boolean,
49+
): Notification {
50+
// Create notification channel
51+
val channelName = applicationContext.getString(R.string.notification_restart_channel)
52+
val channel =
53+
NotificationChannel(
54+
NOTIFICATION_CHANNEL,
55+
channelName,
56+
NotificationManager.IMPORTANCE_HIGH
57+
)
58+
val nm = applicationContext.getSystemService<NotificationManager>()!!
59+
nm.createNotificationChannel(channel)
60+
61+
val pi = if (accessibilityGranted) {
62+
val intent = Intent(applicationContext, PocketService::class.java)
63+
PendingIntent.getForegroundService(
64+
applicationContext,
65+
NOTIFICATION_ID,
66+
intent,
67+
PendingIntent.FLAG_IMMUTABLE
68+
)
69+
} else {
70+
// We can not restart the service, because the system has taken the accessibility
71+
// permission of the app.
72+
val intent = Intent(applicationContext, MainActivity::class.java)
73+
PendingIntent.getActivity(
74+
applicationContext,
75+
NOTIFICATION_ID,
76+
intent,
77+
PendingIntent.FLAG_IMMUTABLE
78+
)
79+
}
80+
81+
val description = applicationContext.getString(R.string.notification_restart_description)
82+
return NotificationCompat.Builder(applicationContext, NOTIFICATION_CHANNEL)
83+
.setAutoCancel(true)
84+
.setSmallIcon(R.drawable.ic_outline_lock)
85+
.setContentIntent(pi)
86+
.setColor(0xFFf4ff81.toInt())
87+
.setShowWhen(false)
88+
.setPriority(NotificationCompat.PRIORITY_HIGH)
89+
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
90+
.setContentTitle(applicationContext.getString(R.string.notification_restart_title))
91+
.setContentText(description)
92+
.setStyle(NotificationCompat.BigTextStyle().bigText(description))
93+
.build()
94+
}
2295
}

app/src/main/java/com/artemchep/pocketmode/util/ObserverFlow.kt

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,7 @@ inline fun <T> observerFlow(
1515
try {
1616
unregister = withContext(Dispatchers.Main) {
1717
val setter = { value: T ->
18-
try {
19-
offer(value)
20-
} catch (e: Exception) {
21-
// Do nothing.
22-
}
18+
trySend(value)
2319
}
2420
register(setter)
2521
}

app/src/main/res/layout/layout_access.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,9 @@
4343
android:paddingBottom="12dp"
4444
android:text="@string/access_accessibility_service"
4545
android:textAppearance="?textAppearanceBodyLarge"
46-
android:textColor="?colorTertiary"
46+
android:textColor="?colorError"
4747
app:drawableStartCompat="@drawable/ic_outline_accessibility"
48-
app:drawableTint="?colorTertiary"
48+
app:drawableTint="?colorError"
4949
app:layout_constraintEnd_toEndOf="parent"
5050
app:layout_constraintStart_toStartOf="parent"
5151
app:layout_constraintTop_toBottomOf="@id/access" />

app/src/main/res/values/strings.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@
5353
<string name="notification_pocket_channel">Pocket mode service notification</string>
5454
<string name="notification_pocket_title">Pocket mode is running</string>
5555
<string name="notification_pocket_description">Long press the notification and hide it, if you don\'t want to see it constantly.</string>
56+
<string name="notification_restart_channel">Pocket mode restart notification</string>
57+
<string name="notification_restart_title">Pocket mode has been stopped by the system</string>
58+
<string name="notification_restart_description">Tap to restart the service</string>
5659

5760
<string name="coming_soon">Coming soon</string>
5861
<string name="error">Oh, snap!</string>

0 commit comments

Comments
 (0)