Skip to content

Commit

Permalink
request proper permission to access files
Browse files Browse the repository at this point in the history
add icons for a shortcut
  • Loading branch information
sdex committed Sep 13, 2022
1 parent f5e4f57 commit 2b6d2f6
Show file tree
Hide file tree
Showing 14 changed files with 220 additions and 1,091 deletions.
6 changes: 4 additions & 2 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ android {

defaultConfig {
applicationId "com.activitymanager"
minSdkVersion 16
minSdkVersion 21
targetSdkVersion 33
versionCode 416
versionName "4.3.0"
Expand Down Expand Up @@ -78,7 +78,7 @@ kapt {
}

dependencies {
implementation "androidx.activity:activity-ktx:1.5.1"
implementation "androidx.activity:activity-ktx:1.6.0-rc02"
implementation "androidx.appcompat:appcompat:1.5.1"
implementation "androidx.browser:browser:1.4.0"
implementation "androidx.constraintlayout:constraintlayout:2.1.4"
Expand All @@ -97,6 +97,8 @@ dependencies {
implementation "com.google.dagger:hilt-android:$hilt_version"
kapt "com.google.dagger:hilt-compiler:$hilt_version"
implementation "com.jakewharton.timber:timber:5.0.1"
implementation "com.maltaisn:icondialog:3.3.0"
implementation "com.maltaisn:iconpack-community-material:5.3.45"
implementation "com.simplecityapps:recyclerview-fastscroll:2.0.0"
implementation "com.tomergoldst.android:tooltips:1.0.11"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4"
Expand Down
7 changes: 6 additions & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@
xmlns:tools="http://schemas.android.com/tools"
package="com.sdex.activityrunner">

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="29"
tools:ignore="ScopedStorage" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" />
<uses-permission
Expand Down
30 changes: 30 additions & 0 deletions app/src/main/assets/licenses.html
Original file line number Diff line number Diff line change
Expand Up @@ -64,5 +64,35 @@
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
</pre>

<ul>
<li>Icon picker dialog (<a href="https://github.com/maltaisn/icondialoglib"
target="_blank">https://github.com/maltaisn/icondialoglib</a>)
</li>
</ul>
<pre> <br/>Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
</pre>

<ul>
<li>Material Design Icons (<a href="http://materialdesignicons.com/"
target="_blank">http://materialdesignicons.com/</a>)
</li>
</ul>
<pre>
Copyright (c) 2014, Austin Andrews (http://materialdesignicons.com/),
with Reserved Font Name Material Design Icons.
Copyright (c) 2014, Google (http://www.google.com/design/)
uses the license at https://github.com/google/material-design-icons/blob/master/LICENSE

This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
</pre>

</body>
</html>
Original file line number Diff line number Diff line change
Expand Up @@ -7,38 +7,59 @@ import android.graphics.Bitmap
import android.graphics.drawable.Drawable
import android.net.Uri
import android.os.Bundle
import android.view.View
import android.widget.PopupMenu
import android.widget.Toast
import androidx.activity.result.PickVisualMediaRequest
import androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia
import androidx.activity.result.contract.ActivityResultContracts.RequestPermission
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.core.graphics.drawable.toBitmap
import androidx.core.view.isInvisible
import androidx.core.view.isVisible
import com.bumptech.glide.request.RequestOptions
import com.bumptech.glide.request.target.SimpleTarget
import com.bumptech.glide.request.target.CustomTarget
import com.bumptech.glide.request.transition.Transition
import com.maltaisn.icondialog.IconDialog
import com.maltaisn.icondialog.IconDialogSettings
import com.maltaisn.icondialog.data.Icon
import com.maltaisn.icondialog.pack.IconDrawableLoader
import com.maltaisn.icondialog.pack.IconPack
import com.maltaisn.icondialog.pack.IconPackLoader
import com.maltaisn.iconpack.mdi.createMaterialDesignIconPack
import com.sdex.activityrunner.R
import com.sdex.activityrunner.app.ActivityModel
import com.sdex.activityrunner.databinding.ActivityAddShortcutBinding
import com.sdex.activityrunner.db.history.HistoryModel
import com.sdex.activityrunner.extensions.doAfterMeasure
import com.sdex.activityrunner.extensions.resolveColorAttr
import com.sdex.activityrunner.extensions.serializable
import com.sdex.activityrunner.glide.GlideApp
import com.sdex.activityrunner.intent.converter.HistoryToLaunchParamsConverter
import com.sdex.activityrunner.intent.converter.LaunchParamsToIntentConverter
import com.sdex.activityrunner.preferences.TooltipPreferences
import com.sdex.activityrunner.util.IntentUtils
import com.sdex.commons.content.ContentManager
import com.sdex.commons.content.getStoragePermission
import com.sdex.commons.content.isStoragePermissionGranted
import com.tomergoldst.tooltips.ToolTip
import com.tomergoldst.tooltips.ToolTipsManager
import timber.log.Timber

class AddShortcutDialogActivity : AppCompatActivity(), ContentManager.PickContentListener {
class AddShortcutDialogActivity : AppCompatActivity(), IconDialog.Callback {

private lateinit var binding: ActivityAddShortcutBinding

private var contentManager: ContentManager? = null
private var bitmap: Bitmap? = null
private val toolTipsManager = ToolTipsManager()
private val pickMedia = registerForActivityResult(PickVisualMedia()) { uri ->
if (uri != null) {
loadIcon(uri)
}
}
private val requestPermission = registerForActivityResult(RequestPermission()) { granted ->
if (granted) {
pickImage()
}
}

private var bitmap: Bitmap? = null
private var iconPack: IconPack? = null

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Expand All @@ -56,7 +77,7 @@ class AddShortcutDialogActivity : AppCompatActivity(), ContentManager.PickConten
.load(activityModel)
.error(R.mipmap.ic_launcher)
.apply(RequestOptions().centerCrop())
.into(object : SimpleTarget<Drawable>() {
.into(object : CustomTarget<Drawable>() {
override fun onResourceReady(
resource: Drawable,
transition: Transition<in Drawable>?
Expand All @@ -65,21 +86,30 @@ class AddShortcutDialogActivity : AppCompatActivity(), ContentManager.PickConten
binding.icon.setImageDrawable(resource)
showTooltip()
}

override fun onLoadCleared(placeholder: Drawable?) {
}
})

val loader = IconPackLoader(applicationContext)
iconPack = createMaterialDesignIconPack(loader)
iconPack?.loadDrawables(loader.drawableLoader)
}

if (historyModel != null) {
binding.icon.setImageResource(R.mipmap.ic_launcher)
}

contentManager = ContentManager(this, this)

binding.icon.setOnClickListener {
toolTipsManager.dismissAll()
if (activityModel != null) {
contentManager?.pickContent(ContentManager.Content.IMAGE)
showIconMenu(it)
} else {
Toast.makeText(this, R.string.error_intent_shortcut_icon, Toast.LENGTH_LONG).show()
Toast.makeText(
this,
R.string.error_intent_shortcut_icon,
Toast.LENGTH_LONG
).show()
}
}

Expand Down Expand Up @@ -119,54 +149,73 @@ class AddShortcutDialogActivity : AppCompatActivity(), ContentManager.PickConten
binding.icon.doAfterMeasure {
val builder = ToolTip.Builder(
this@AddShortcutDialogActivity,
binding.icon, binding.content,
"Tap to change the icon", ToolTip.POSITION_BELOW
)
builder.setBackgroundColor(
ContextCompat.getColor(
this@AddShortcutDialogActivity, R.color.blue_light
)
binding.icon,
binding.content,
getString(R.string.shortcut_set_icon_tooltip),
ToolTip.POSITION_BELOW
)
builder.setBackgroundColor(resolveColorAttr(R.attr.colorTertiary))
builder.setTextAppearance(R.style.TooltipTextAppearance)
toolTipsManager.show(builder.build())
preferences.showChangeIcon = false
}
}
}

private fun createHistoryModelShortcut(historyModel: HistoryModel, shortcutName: String) {
val historyToLaunchParamsConverter = HistoryToLaunchParamsConverter(historyModel)
val launchParams = historyToLaunchParamsConverter.convert()
val converter = LaunchParamsToIntentConverter(launchParams)
val intent = converter.convert()
IntentUtils.createLauncherIcon(this, shortcutName, intent, R.mipmap.ic_launcher)
}

override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
contentManager?.onSaveInstanceState(outState)
}
override val iconDialogIconPack: IconPack?
get() = iconPack

override fun onRestoreInstanceState(savedInstanceState: Bundle) {
super.onRestoreInstanceState(savedInstanceState)
contentManager?.onRestoreInstanceState(savedInstanceState)
override fun onIconDialogIconsSelected(dialog: IconDialog, icons: List<Icon>) {
val icon = icons.first()
if (icon.drawable == null) {
IconDrawableLoader(this).loadDrawable(icon)
}
val resource = icon.drawable
bitmap = resource?.toBitmap()
binding.icon.setImageDrawable(resource)
}

override fun onRequestPermissionsResult(
requestCode: Int, permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
contentManager?.onRequestPermissionsResult(requestCode, permissions, grantResults)
private fun showIconMenu(it: View?) {
val popupMenu = PopupMenu(this, it)
popupMenu.inflate(R.menu.shortcut_icon)
popupMenu.setOnMenuItemClickListener { menuItem ->
if (menuItem.itemId == R.id.pick_gallery) {
if (isStoragePermissionGranted(this)) {
pickImage()
} else {
requestPermission.launch(getStoragePermission())
}
} else if (menuItem.itemId == R.id.pick_icon) {
if (iconPack != null) {
val iconDialog = IconDialog.newInstance(IconDialogSettings {
showSelectBtn = false
})
iconDialog.show(supportFragmentManager, ICON_DIALOG_TAG)
} else {
Toast.makeText(
this,
R.string.icons_loading_error,
Toast.LENGTH_SHORT
).show()
}
}
true
}
popupMenu.show()
}

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
contentManager?.onActivityResult(requestCode, resultCode, data)
private fun pickImage() {
pickMedia.launch(
PickVisualMediaRequest(PickVisualMedia.ImageOnly)
)
}

override fun onContentLoaded(uri: Uri?, contentType: String?) {
uri?.let { loadIcon(uri) }
private fun createHistoryModelShortcut(historyModel: HistoryModel, shortcutName: String) {
val historyToLaunchParamsConverter = HistoryToLaunchParamsConverter(historyModel)
val launchParams = historyToLaunchParamsConverter.convert()
val converter = LaunchParamsToIntentConverter(launchParams)
val intent = converter.convert()
IntentUtils.createLauncherIcon(this, shortcutName, intent, R.mipmap.ic_launcher)
}

private fun loadIcon(uri: Uri) {
Expand All @@ -177,39 +226,22 @@ class AddShortcutDialogActivity : AppCompatActivity(), ContentManager.PickConten
.load(uri)
.error(R.mipmap.ic_launcher)
.apply(RequestOptions().centerCrop().override(size))
.into(object : SimpleTarget<Bitmap>(size, size) {
.into(object : CustomTarget<Bitmap>(size, size) {
override fun onResourceReady(resource: Bitmap, transition: Transition<in Bitmap>?) {
bitmap = resource
binding.icon.setImageBitmap(resource)
hideProgress()
}
})
}

override fun onStartContentLoading() {
binding.progress.isVisible = true
binding.icon.isInvisible = true
}

override fun onError(error: String?) {
Timber.e("Failed to load image: %s", error)
Toast.makeText(this, "Failed to load image", Toast.LENGTH_SHORT).show()
hideProgress()
}

override fun onCanceled() {
hideProgress()
}

private fun hideProgress() {
binding.progress.isVisible = false
binding.icon.isVisible = true
override fun onLoadCleared(placeholder: Drawable?) {
}
})
}

companion object {

private const val ARG_ACTIVITY_MODEL = "arg_activity_model"
private const val ARG_HISTORY_MODEL = "arg_history_model"
private const val ICON_DIALOG_TAG = "icons_dialog"

fun start(context: Context, activityModel: ActivityModel) {
context.startActivity(
Expand Down
25 changes: 25 additions & 0 deletions app/src/main/java/com/sdex/activityrunner/util/PermissionHelper.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
@file:JvmName("PermissionHelper")

package com.sdex.commons.content

import android.Manifest.permission.READ_EXTERNAL_STORAGE
import android.Manifest.permission.READ_MEDIA_IMAGES
import android.Manifest.permission.WRITE_EXTERNAL_STORAGE
import android.content.Context
import android.content.pm.PackageManager
import android.os.Build
import androidx.core.content.ContextCompat

fun getStoragePermission(): String = when {
Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU -> READ_MEDIA_IMAGES
Build.VERSION.SDK_INT > Build.VERSION_CODES.Q -> READ_EXTERNAL_STORAGE
else -> WRITE_EXTERNAL_STORAGE
}

fun isStoragePermissionGranted(context: Context): Boolean {
return hasPermission(context, getStoragePermission())
}

private fun hasPermission(context: Context, permission: String): Boolean =
ContextCompat.checkSelfPermission(context, permission) ==
PackageManager.PERMISSION_GRANTED
Loading

0 comments on commit 2b6d2f6

Please sign in to comment.