Refactor app shortcut handling (#1392)

This commit is contained in:
Harsh Shandilya 2021-04-21 18:07:35 +05:30 committed by GitHub
parent 53c3431ef0
commit 6ff01f5e1e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 112 additions and 55 deletions

View file

@ -8,11 +8,6 @@ import android.Manifest
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.content.pm.ShortcutInfo
import android.content.pm.ShortcutInfo.Builder
import android.content.pm.ShortcutManager
import android.graphics.drawable.Icon
import android.os.Build
import android.os.Bundle
import android.view.KeyEvent
import android.view.Menu
@ -21,11 +16,9 @@ import android.view.MenuItem.OnActionExpandListener
import androidx.activity.result.contract.ActivityResultContracts.RequestPermission
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
import androidx.activity.viewModels
import androidx.annotation.RequiresApi
import androidx.appcompat.widget.SearchView
import androidx.appcompat.widget.SearchView.OnQueryTextListener
import androidx.core.content.edit
import androidx.core.content.getSystemService
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.commit
import androidx.lifecycle.ViewModelProvider
@ -40,6 +33,7 @@ import com.github.michaelbull.result.onFailure
import com.github.michaelbull.result.runCatching
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.textfield.TextInputEditText
import dagger.hilt.android.AndroidEntryPoint
import dev.msfjarvis.aps.R
import dev.msfjarvis.aps.data.password.PasswordItem
import dev.msfjarvis.aps.data.repo.PasswordRepository
@ -67,9 +61,11 @@ import dev.msfjarvis.aps.util.extensions.sharedPrefs
import dev.msfjarvis.aps.util.settings.AuthMode
import dev.msfjarvis.aps.util.settings.GitSettings
import dev.msfjarvis.aps.util.settings.PreferenceKeys
import dev.msfjarvis.aps.util.shortcuts.ShortcutHandler
import dev.msfjarvis.aps.util.viewmodel.SearchableRepositoryViewModel
import java.io.File
import java.lang.Character.UnicodeBlock
import javax.inject.Inject
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@ -77,8 +73,10 @@ import org.eclipse.jgit.api.Git
const val PASSWORD_FRAGMENT_TAG = "PasswordsList"
@AndroidEntryPoint
class PasswordStore : BaseGitActivity() {
@Inject lateinit var shortcutHandler: ShortcutHandler
private lateinit var searchItem: MenuItem
private val settings by lazy { sharedPrefs }
@ -440,50 +438,7 @@ class PasswordStore : BaseGitActivity() {
startActivity(decryptIntent)
// Adds shortcut
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
addShortcut(item, authDecryptIntent)
}
}
@RequiresApi(Build.VERSION_CODES.N_MR1)
private fun addShortcut(item: PasswordItem, intent: Intent) {
val shortcutManager: ShortcutManager = getSystemService() ?: return
val shortcut =
Builder(this, item.fullPathToParent)
.setShortLabel(item.toString())
.setLongLabel(item.fullPathToParent + item.toString())
.setIcon(Icon.createWithResource(this, R.drawable.ic_lock_open_24px))
.setIntent(intent)
.build()
val shortcuts = shortcutManager.dynamicShortcuts
// If we're above or equal to the maximum shortcuts allowed, drop the last item.
if (shortcuts.size >= MAX_SHORTCUT_COUNT) {
shortcuts.removeLast()
}
// Reverse the list so we can append our new shortcut at the 'end'.
shortcuts.reverse()
shortcuts.add(shortcut)
// Reverse it again, so the previous items are now in the correct order and our new item
// is at the front like it's supposed to.
shortcuts.reverse()
// Write back the new shortcuts.
shortcutManager.dynamicShortcuts = shortcuts.map(::rebuildShortcut)
}
/**
* Takes an existing [ShortcutInfo] and builds a fresh instance of [ShortcutInfo] with the same
* data, which ensures that the get/set dance in [addShortcut] does not cause invalidation of icon
* assets, resulting in invisible icons in all but the newest launcher shortcut.
*/
@RequiresApi(Build.VERSION_CODES.N_MR1)
private fun rebuildShortcut(shortcut: ShortcutInfo): ShortcutInfo {
// Non-null assertions are fine since we know these values aren't null.
return Builder(this@PasswordStore, shortcut.id)
.setShortLabel(shortcut.shortLabel!!)
.setLongLabel(shortcut.longLabel!!)
.setIcon(Icon.createWithResource(this@PasswordStore, R.drawable.ic_lock_open_24px))
.setIntent(shortcut.intent!!)
.build()
shortcutHandler.addDynamicShortcut(item, authDecryptIntent)
}
private fun validateState(): Boolean {
@ -693,10 +648,6 @@ class PasswordStore : BaseGitActivity() {
companion object {
// The max shortcut count from the system is set to 15 for some godforsaken reason, which
// makes zero sense and is why our update logic just never worked. Capping it at 4 which is
// what most launchers seem to have agreed upon is the only reasonable solution.
private const val MAX_SHORTCUT_COUNT = 4
const val REQUEST_ARG_PATH = "PATH"
private fun isPrintable(c: Char): Boolean {
val block = UnicodeBlock.of(c)

View file

@ -0,0 +1,106 @@
/*
* Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved.
* SPDX-License-Identifier: GPL-3.0-only
*/
package dev.msfjarvis.aps.util.shortcuts
import android.content.Context
import android.content.Intent
import android.content.pm.ShortcutInfo
import android.content.pm.ShortcutManager
import android.graphics.drawable.Icon
import android.os.Build
import androidx.annotation.RequiresApi
import androidx.core.content.getSystemService
import com.github.ajalt.timberkt.d
import dagger.Reusable
import dagger.hilt.android.qualifiers.ApplicationContext
import dev.msfjarvis.aps.R
import dev.msfjarvis.aps.data.password.PasswordItem
import javax.inject.Inject
@Reusable
class ShortcutHandler
@Inject
constructor(
@ApplicationContext val context: Context,
) {
private companion object {
// The max shortcut count from the system is set to 15 for some godforsaken reason, which
// makes zero sense and is why our update logic just never worked. Capping it at 4 which is
// what most launchers seem to have agreed upon is the only reasonable solution.
private const val MAX_SHORTCUT_COUNT = 4
}
/**
* Creates a
* [dynamic shortcut](https://developer.android.com/guide/topics/ui/shortcuts/creating-shortcuts#dynamic)
* that shows up with the app icon on long press. The list of items is capped to
* [MAX_SHORTCUT_COUNT] and older items are removed by a simple LRU sweep.
*/
fun addDynamicShortcut(item: PasswordItem, intent: Intent) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N_MR1) return
val shortcutManager: ShortcutManager = context.getSystemService() ?: return
val shortcut = buildShortcut(item, intent)
val shortcuts = shortcutManager.dynamicShortcuts
// If we're above or equal to the maximum shortcuts allowed, drop the last item.
if (shortcuts.size >= MAX_SHORTCUT_COUNT) {
shortcuts.removeLast()
}
// Reverse the list so we can append our new shortcut at the 'end'.
shortcuts.reverse()
shortcuts.add(shortcut)
// Reverse it again, so the previous items are now in the correct order and our new item
// is at the front like it's supposed to.
shortcuts.reverse()
// Write back the new shortcuts.
shortcutManager.dynamicShortcuts = shortcuts.map(::rebuildShortcut)
}
/**
* Creates a
* [pinned shortcut](https://developer.android.com/guide/topics/ui/shortcuts/creating-shortcuts#pinned)
* which presents a UI to users, allowing manual placement on the launcher screen. This method is
* a no-op if the user's default launcher does not support pinned shortcuts.
*/
fun addPinnedShortcut(item: PasswordItem, intent: Intent) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return
val shortcutManager: ShortcutManager = context.getSystemService() ?: return
if (!shortcutManager.isRequestPinShortcutSupported) {
d { "addPinnedShortcut: pin shortcuts unsupported" }
return
}
val shortcut = buildShortcut(item, intent)
shortcutManager.requestPinShortcut(shortcut, null)
}
/** Creates a [ShortcutInfo] from [item] and assigns [intent] to it. */
@RequiresApi(Build.VERSION_CODES.N_MR1)
private fun buildShortcut(item: PasswordItem, intent: Intent): ShortcutInfo {
return ShortcutInfo.Builder(context, item.fullPathToParent)
.setShortLabel(item.toString())
.setLongLabel(item.fullPathToParent + item.toString())
.setIcon(Icon.createWithResource(context, R.drawable.ic_lock_open_24px))
.setIntent(intent)
.build()
}
/**
* Takes an existing [ShortcutInfo] and builds a fresh instance of [ShortcutInfo] with the same
* data, which ensures that the get/set dance in [addDynamicShortcut] does not cause invalidation
* of icon assets, resulting in invisible icons in all but the newest launcher shortcut.
*/
@RequiresApi(Build.VERSION_CODES.N_MR1)
private fun rebuildShortcut(shortcut: ShortcutInfo): ShortcutInfo {
// Non-null assertions are fine since we know these values aren't null.
return ShortcutInfo.Builder(context, shortcut.id)
.setShortLabel(shortcut.shortLabel!!)
.setLongLabel(shortcut.longLabel!!)
.setIcon(Icon.createWithResource(context, R.drawable.ic_lock_open_24px))
.setIntent(shortcut.intent!!)
.build()
}
}