Skip to content
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

Lock widget if app is locked #87

Merged
merged 1 commit into from
Nov 1, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ import android.content.Intent
import android.content.IntentFilter
import androidx.appcompat.app.AppCompatDelegate
import androidx.lifecycle.Observer
import com.philkes.notallyx.presentation.view.misc.BetterLiveData
import com.philkes.notallyx.presentation.view.misc.BiometricLock.enabled
import com.philkes.notallyx.presentation.view.misc.Theme
import com.philkes.notallyx.presentation.widget.WidgetProvider
import com.philkes.notallyx.utils.backup.Export.scheduleAutoBackup
import com.philkes.notallyx.utils.security.UnlockReceiver

Expand All @@ -16,7 +18,7 @@ class NotallyXApplication : Application() {
private lateinit var preferences: Preferences
private var unlockReceiver: UnlockReceiver? = null

var isLocked = true
val locked = BetterLiveData(true)

override fun onCreate() {
super.onCreate()
Expand Down Expand Up @@ -47,5 +49,7 @@ class NotallyXApplication : Application() {
}
}
preferences.biometricLock.observeForever(biometricLockObserver)

locked.observeForever { isLocked -> WidgetProvider.updateWidgets(this, locked = isLocked) }
}
}
12 changes: 9 additions & 3 deletions app/src/main/java/com/philkes/notallyx/Preferences.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import android.os.Build
import android.preference.PreferenceManager
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKey
import com.philkes.notallyx.data.model.Type
import com.philkes.notallyx.data.model.toPreservedByteArray
import com.philkes.notallyx.data.model.toPreservedString
import com.philkes.notallyx.presentation.view.misc.AutoBackup
Expand Down Expand Up @@ -127,17 +128,22 @@ class Preferences private constructor(app: Application) {

fun getWidgetData(id: Int) = preferences.getLong("widget:$id", 0)

fun getWidgetNoteType(id: Int) =
preferences.getString("widgetNoteType:$id", null)?.let { Type.valueOf(it) }

fun deleteWidget(id: Int) {
editor.remove("widget:$id")
editor.remove("widgetNoteType:$id")
editor.commit()
}

fun updateWidget(id: Int, noteId: Long) {
fun updateWidget(id: Int, noteId: Long, noteType: Type) {
editor.putLong("widget:$id", noteId)
editor.putString("widgetNoteType:$id", noteType.name)
editor.commit()
}

fun getUpdatableWidgets(noteIds: LongArray): List<Pair<Int, Long>> {
fun getUpdatableWidgets(noteIds: LongArray? = null): List<Pair<Int, Long>> {
val updatableWidgets = ArrayList<Pair<Int, Long>>()
val pairs = preferences.all
pairs.keys.forEach { key ->
Expand All @@ -148,7 +154,7 @@ class Preferences private constructor(app: Application) {
if (id != null) {
val value = pairs[key] as? Long
if (value != null) {
if (noteIds.contains(value)) {
if (noteIds == null || noteIds.contains(value)) {
updatableWidgets.add(Pair(id, value))
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,11 @@ class ConfigureWidgetActivity : PickNoteActivity() {
override fun onClick(position: Int) {
if (position != -1) {
val preferences = Preferences.getInstance(application)
val noteId = (adapter.getItem(position) as BaseNote).id
preferences.updateWidget(id, noteId)
val baseNote = adapter.getItem(position) as BaseNote
preferences.updateWidget(id, baseNote.id, baseNote.type)

val manager = AppWidgetManager.getInstance(this)
WidgetProvider.updateWidget(this, manager, id, noteId)
WidgetProvider.updateWidget(this, manager, id, baseNote.id, baseNote.type)

val success = Intent()
success.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, id)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,7 @@ abstract class LockedActivity<T : ViewBinding> : AppCompatActivity() {
biometricAuthenticationActivityResultLauncher =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == Activity.RESULT_OK) {
notallyXApplication.isLocked = false
show()
unlock()
} else {
finish()
}
Expand Down Expand Up @@ -67,15 +66,17 @@ abstract class LockedActivity<T : ViewBinding> : AppCompatActivity() {
preferences.iv!!,
biometricAuthenticationActivityResultLauncher,
R.string.unlock,
onSuccess = {
notallyXApplication.isLocked = false
show()
},
onSuccess = { unlock() },
) {
finish()
}
}

private fun unlock() {
notallyXApplication.locked.value = false
show()
}

protected fun show() {
binding.root.visibility = VISIBLE
}
Expand All @@ -88,7 +89,7 @@ abstract class LockedActivity<T : ViewBinding> : AppCompatActivity() {
val keyguardManager: KeyguardManager =
this.getSystemService(KEYGUARD_SERVICE) as KeyguardManager
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
(keyguardManager.isDeviceLocked || notallyXApplication.isLocked)
(keyguardManager.isDeviceLocked || notallyXApplication.locked.value)
} else {
false
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -564,7 +564,8 @@ class SettingsFragment : Fragment() {
encryptDatabase(requireContext(), passphrase)
model.savePreference(BiometricLock, enabled)
}
(activity?.application as NotallyXApplication).isLocked = false
val app = (activity?.application as NotallyXApplication)
app.locked.value = false
showBiometricsEnabledToast()
},
) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
package com.philkes.notallyx.presentation.widget

import android.app.Application
import android.content.Intent
import android.graphics.Paint
import android.os.Build
import android.util.TypedValue
import android.view.View
import android.widget.RemoteViews
import android.widget.RemoteViewsService
import com.philkes.notallyx.NotallyXApplication
import com.philkes.notallyx.Preferences
import com.philkes.notallyx.R
import com.philkes.notallyx.data.NotallyDatabase
Expand All @@ -16,8 +16,11 @@ import com.philkes.notallyx.data.model.Type
import com.philkes.notallyx.presentation.view.misc.TextSize
import com.philkes.notallyx.presentation.widget.WidgetProvider.Companion.getSelectNoteIntent

class WidgetFactory(private val app: Application, private val id: Long, private val widgetId: Int) :
RemoteViewsService.RemoteViewsFactory {
class WidgetFactory(
private val app: NotallyXApplication,
private val id: Long,
private val widgetId: Int,
) : RemoteViewsService.RemoteViewsFactory {

private var baseNote: BaseNote? = null
private lateinit var database: NotallyDatabase
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import com.philkes.notallyx.R
import com.philkes.notallyx.data.NotallyDatabase
import com.philkes.notallyx.data.dao.BaseNoteDao
import com.philkes.notallyx.data.model.BaseNote
import com.philkes.notallyx.data.model.Type
import com.philkes.notallyx.data.model.findChildrenPositions
import com.philkes.notallyx.data.model.findParentPosition
import com.philkes.notallyx.presentation.activity.ConfigureWidgetActivity
Expand Down Expand Up @@ -107,27 +108,6 @@ class WidgetProvider : AppWidgetProvider() {
}
}

private fun updateWidgets(context: Context, noteIds: LongArray) {
val app = context.applicationContext as Application
val preferences = Preferences.getInstance(app)

val manager = AppWidgetManager.getInstance(context)
val updatableWidgets = preferences.getUpdatableWidgets(noteIds)

updatableWidgets.forEach { pair -> updateWidget(context, manager, pair.first, pair.second) }
}

private fun openActivity(context: Context, originalIntent: Intent, clazz: Class<*>) {
val id = originalIntent.getLongExtra(Constants.SelectedBaseNote, 0)
val widgetId = originalIntent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, 0)
val intent = Intent(context, clazz)
intent.putExtra(Constants.SelectedBaseNote, id)
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId)
intent.data = originalIntent.data
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
context.startActivity(intent)
}

override fun onDeleted(context: Context, appWidgetIds: IntArray) {
val app = context.applicationContext as Application
val preferences = Preferences.getInstance(app)
Expand All @@ -145,31 +125,114 @@ class WidgetProvider : AppWidgetProvider() {

appWidgetIds.forEach { id ->
val noteId = preferences.getWidgetData(id)
updateWidget(context, appWidgetManager, id, noteId)
val noteType = preferences.getWidgetNoteType(id)
updateWidget(context, appWidgetManager, id, noteId, noteType)
}
}

companion object {

fun updateWidget(context: Context, manager: AppWidgetManager, id: Int, noteId: Long) {
fun updateWidgets(context: Context, noteIds: LongArray? = null, locked: Boolean = false) {
val app = context.applicationContext as Application
val preferences = Preferences.getInstance(app)

val manager = AppWidgetManager.getInstance(context)
val updatableWidgets = preferences.getUpdatableWidgets(noteIds)

updatableWidgets.forEach { (id, noteId) ->
updateWidget(
context,
manager,
id,
noteId,
preferences.getWidgetNoteType(id),
locked = locked,
)
}
}

fun updateWidget(
context: Context,
manager: AppWidgetManager,
id: Int,
noteId: Long,
noteType: Type?,
locked: Boolean = false,
) {
// Widgets displaying the same note share the same factory since only the noteId is
// embedded
val intent = Intent(context, WidgetService::class.java)
intent.putExtra(Constants.SelectedBaseNote, noteId)
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, id)
embedIntentExtras(intent)

val view = RemoteViews(context.packageName, R.layout.widget)
view.setRemoteAdapter(R.id.ListView, intent)
view.setEmptyView(R.id.ListView, R.id.Empty)

view.setOnClickFillInIntent(R.id.Empty, getSelectNoteIntent(id))
val view =
if (!locked) {
RemoteViews(context.packageName, R.layout.widget).apply {
setRemoteAdapter(R.id.ListView, intent)
setEmptyView(R.id.ListView, R.id.Empty)
setOnClickFillInIntent(R.id.Empty, getSelectNoteIntent(id))
setPendingIntentTemplate(R.id.ListView, getOpenNoteIntent(context, noteId))
}
} else {
RemoteViews(context.packageName, R.layout.widget_locked).apply {
noteType?.let {
val openNoteIntent =
when (it) {
Type.NOTE -> Intent(context, EditNoteActivity::class.java)
Type.LIST -> Intent(context, EditListActivity::class.java)
}.apply {
putExtra(Constants.SelectedBaseNote, noteId)
addFlags(
Intent.FLAG_ACTIVITY_NEW_TASK or
Intent.FLAG_ACTIVITY_CLEAR_TASK
)
}
val lockedPendingIntent =
PendingIntent.getActivity(
context,
0,
openNoteIntent,
PendingIntent.FLAG_UPDATE_CURRENT or
PendingIntent.FLAG_IMMUTABLE,
)
setOnClickPendingIntent(R.id.Layout, lockedPendingIntent)
setOnClickPendingIntent(R.id.Text, lockedPendingIntent)
}
setTextViewCompoundDrawablesRelative(
R.id.Text,
0,
R.drawable.lock_big,
0,
0,
)
}
}
manager.updateAppWidget(id, view)
if (!locked) {
manager.notifyAppWidgetViewDataChanged(id, R.id.ListView)
}
}

val openNote = getOpenNoteIntent(context, noteId)
view.setPendingIntentTemplate(R.id.ListView, openNote)
private fun openActivity(context: Context, originalIntent: Intent, clazz: Class<*>) {
val id = originalIntent.getLongExtra(Constants.SelectedBaseNote, 0)
val widgetId = originalIntent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, 0)
context.startActivity(createIntent(context, clazz, id, widgetId, originalIntent))
}

manager.updateAppWidget(id, view)
manager.notifyAppWidgetViewDataChanged(id, R.id.ListView)
private fun createIntent(
context: Context,
clazz: Class<*>,
noteId: Long,
widgetId: Int,
originalIntent: Intent? = null,
): Intent {
val intent = Intent(context, clazz)
intent.putExtra(Constants.SelectedBaseNote, noteId)
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId)
originalIntent?.let { intent.data = it.data }
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
return intent
}

fun sendBroadcast(app: Application, ids: LongArray) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@ package com.philkes.notallyx.presentation.widget
import android.appwidget.AppWidgetManager
import android.content.Intent
import android.widget.RemoteViewsService
import com.philkes.notallyx.NotallyXApplication
import com.philkes.notallyx.presentation.view.Constants

class WidgetService : RemoteViewsService() {

override fun onGetViewFactory(intent: Intent): RemoteViewsFactory {
val id = intent.getLongExtra(Constants.SelectedBaseNote, 0)
val widgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, 0)
return WidgetFactory(application, id, widgetId)
return WidgetFactory(application as NotallyXApplication, id, widgetId)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ class UnlockReceiver(private val application: NotallyXApplication) : BroadcastRe

override fun onReceive(context: Context?, intent: Intent) {
if (intent.action == Intent.ACTION_SCREEN_OFF) {
application.isLocked = true
application.locked.value = true
}
}
}
10 changes: 10 additions & 0 deletions app/src/main/res/drawable/lock_big.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="?android:textColorPrimary"
android:pathData="M240,880Q207,880 183.5,856.5Q160,833 160,800L160,400Q160,367 183.5,343.5Q207,320 240,320L280,320L280,240Q280,157 338.5,98.5Q397,40 480,40Q563,40 621.5,98.5Q680,157 680,240L680,320L720,320Q753,320 776.5,343.5Q800,367 800,400L800,800Q800,833 776.5,856.5Q753,880 720,880L240,880ZM240,800L720,800Q720,800 720,800Q720,800 720,800L720,400Q720,400 720,400Q720,400 720,400L240,400Q240,400 240,400Q240,400 240,400L240,800Q240,800 240,800Q240,800 240,800ZM480,680Q513,680 536.5,656.5Q560,633 560,600Q560,567 536.5,543.5Q513,520 480,520Q447,520 423.5,543.5Q400,567 400,600Q400,633 423.5,656.5Q447,680 480,680ZM360,320L600,320L600,240Q600,190 565,155Q530,120 480,120Q430,120 395,155Q360,190 360,240L360,320ZM240,800Q240,800 240,800Q240,800 240,800L240,400Q240,400 240,400Q240,400 240,400L240,400Q240,400 240,400Q240,400 240,400L240,800Q240,800 240,800Q240,800 240,800Z"/>
</vector>
21 changes: 21 additions & 0 deletions app/src/main/res/layout/widget_locked.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/Layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?android:colorBackground">

<TextView
android:id="@+id/Text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clickable="true"
android:focusable="true"
android:gravity="center"
android:layout_gravity="center"
android:text="@string/locked"
android:textSize="18sp"
/>

</FrameLayout>
1 change: 1 addition & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@
<string name="security">Security</string>

<string name="biometric_lock">Lock app with device biometric or PIN</string>
<string name="locked">Locked</string>
<string name="enabled">Enabled</string>
<string name="disabled">Disabled</string>

Expand Down