├── app ├── .gitignore ├── src │ └── main │ │ ├── ic_launcher-playstore.png │ │ ├── java │ │ └── com │ │ │ └── jintin │ │ │ └── quickroute │ │ │ ├── QuickRouteApp.kt │ │ │ ├── base │ │ │ ├── ItemComparable.kt │ │ │ ├── BaseDao.kt │ │ │ ├── ItemCallback.kt │ │ │ └── RecyclerViewUtils.kt │ │ │ ├── db │ │ │ ├── ActionDao.kt │ │ │ ├── QuickRouteDB.kt │ │ │ └── ActionConverter.kt │ │ │ ├── data │ │ │ ├── AppInfo.kt │ │ │ ├── IconUtils.kt │ │ │ ├── Action.kt │ │ │ ├── IntentUtils.kt │ │ │ └── Extra.kt │ │ │ ├── action │ │ │ ├── ActionListViewModel.kt │ │ │ ├── ActionListAdapter.kt │ │ │ ├── SelectActionBottomSheet.kt │ │ │ └── ActionListActivity.kt │ │ │ ├── AppModule.kt │ │ │ ├── DevRouteService.kt │ │ │ ├── select │ │ │ ├── ActListAdapter.kt │ │ │ ├── AppListViewModel.kt │ │ │ ├── AppListActivity.kt │ │ │ ├── ActListViewModel.kt │ │ │ ├── AppListAdapter.kt │ │ │ └── ActListActivity.kt │ │ │ └── extra │ │ │ ├── ExtraListAdapter.kt │ │ │ ├── EditDialogViewModel.kt │ │ │ ├── ExtraListViewModel.kt │ │ │ ├── ExtraListActivity.kt │ │ │ └── EditDialogFragment.kt │ │ ├── res │ │ ├── mipmap-anydpi-v26 │ │ │ ├── ic_launcher.xml │ │ │ └── ic_launcher_round.xml │ │ ├── menu │ │ │ ├── main.xml │ │ │ └── extra.xml │ │ ├── drawable │ │ │ ├── ic_add.xml │ │ │ ├── ic_done.xml │ │ │ ├── ic_delete.xml │ │ │ ├── ic_edit.xml │ │ │ ├── ic_launcher_foreground.xml │ │ │ └── ic_dev_options.xml │ │ ├── values │ │ │ ├── colors.xml │ │ │ ├── themes.xml │ │ │ └── strings.xml │ │ ├── values-night │ │ │ └── themes.xml │ │ └── layout │ │ │ ├── adapter_act.xml │ │ │ ├── activity_app.xml │ │ │ ├── activity_act.xml │ │ │ ├── activity_extra.xml │ │ │ ├── adapter_app.xml │ │ │ ├── fragment_action_sheet.xml │ │ │ ├── adapter_extra.xml │ │ │ ├── activity_action.xml │ │ │ ├── adapter_action.xml │ │ │ └── fragment_edit_extra.xml │ │ └── AndroidManifest.xml ├── proguard-rules.pro ├── schemas │ └── com.jintin.quickroute.db.QuickRouteDB │ │ └── 1.json └── build.gradle ├── settings.gradle ├── .idea ├── .gitignore ├── compiler.xml ├── vcs.xml ├── google-java-format.xml ├── inspectionProfiles │ └── Project_Default.xml ├── runConfigurations.xml ├── misc.xml ├── gradle.xml └── jarRepositories.xml ├── README └── preview.gif ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .gitignore ├── README.md ├── gradle.properties ├── .circleci └── config.yml ├── gradlew.bat ├── gradlew └── LICENSE /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = "QuickRoute" 2 | include ':app' 3 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /README/preview.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jintin/QuickRoute/HEAD/README/preview.gif -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jintin/QuickRoute/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/main/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jintin/QuickRoute/HEAD/app/src/main/ic_launcher-playstore.png -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/google-java-format.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /app/src/main/java/com/jintin/quickroute/QuickRouteApp.kt: -------------------------------------------------------------------------------- 1 | package com.jintin.quickroute 2 | 3 | import android.app.Application 4 | import dagger.hilt.android.HiltAndroidApp 5 | 6 | @HiltAndroidApp 7 | class QuickRouteApp : Application() -------------------------------------------------------------------------------- /app/src/main/java/com/jintin/quickroute/base/ItemComparable.kt: -------------------------------------------------------------------------------- 1 | package com.jintin.quickroute.base 2 | 3 | interface ItemComparable { 4 | fun areItemsTheSame(target: T): Boolean 5 | 6 | fun areContentsTheSame(target: T): Boolean 7 | } -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Jun 09 19:58:43 CST 2021 2 | distributionBase=GRADLE_USER_HOME 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | local.properties 16 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/menu/main.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/java/com/jintin/quickroute/base/BaseDao.kt: -------------------------------------------------------------------------------- 1 | package com.jintin.quickroute.base 2 | 3 | import androidx.room.Delete 4 | import androidx.room.Insert 5 | import androidx.room.Update 6 | 7 | interface BaseDao { 8 | @Insert 9 | suspend fun insert(data: T) 10 | 11 | @Delete 12 | suspend fun delete(data: T) 13 | 14 | @Update 15 | suspend fun update(data: T) 16 | } -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_add.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/java/com/jintin/quickroute/db/ActionDao.kt: -------------------------------------------------------------------------------- 1 | package com.jintin.quickroute.db 2 | 3 | import androidx.lifecycle.LiveData 4 | import androidx.room.Dao 5 | import androidx.room.Query 6 | import com.jintin.quickroute.base.BaseDao 7 | import com.jintin.quickroute.data.Action 8 | 9 | @Dao 10 | interface ActionDao : BaseDao { 11 | 12 | @Query("SELECT * FROM `action`") 13 | fun list(): LiveData> 14 | 15 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_done.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/java/com/jintin/quickroute/data/AppInfo.kt: -------------------------------------------------------------------------------- 1 | package com.jintin.quickroute.data 2 | 3 | import com.jintin.quickroute.base.ItemComparable 4 | 5 | data class AppInfo( 6 | var name: String, 7 | val packageName: String 8 | ) : ItemComparable { 9 | 10 | override fun areItemsTheSame(target: AppInfo) = this.packageName == target.packageName 11 | 12 | override fun areContentsTheSame(target: AppInfo) = this == target 13 | 14 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_delete.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FBC02D 4 | #FFBB86FC 5 | #FF6200EE 6 | #FF3700B3 7 | #FF03DAC5 8 | #FF018786 9 | #FF000000 10 | #FFFFFFFF 11 | -------------------------------------------------------------------------------- /app/src/main/java/com/jintin/quickroute/data/IconUtils.kt: -------------------------------------------------------------------------------- 1 | package com.jintin.quickroute.base 2 | 3 | import android.content.pm.PackageManager 4 | import android.widget.ImageView 5 | import kotlinx.coroutines.Dispatchers 6 | import kotlinx.coroutines.withContext 7 | 8 | suspend fun PackageManager.loadImage(imageView: ImageView, packageName: String) { 9 | imageView.setImageDrawable(null) 10 | imageView.setImageDrawable( 11 | withContext(Dispatchers.IO) { 12 | getApplicationIcon(packageName) 13 | }) 14 | } -------------------------------------------------------------------------------- /app/src/main/java/com/jintin/quickroute/base/ItemCallback.kt: -------------------------------------------------------------------------------- 1 | package com.jintin.quickroute.base 2 | 3 | import androidx.recyclerview.widget.DiffUtil 4 | 5 | class ItemCallback> : DiffUtil.ItemCallback() { 6 | 7 | override fun areItemsTheSame(oldItem: T, newItem: T): Boolean { 8 | return oldItem.areItemsTheSame(newItem) 9 | } 10 | 11 | override fun areContentsTheSame( 12 | oldItem: T, 13 | newItem: T 14 | ): Boolean { 15 | return oldItem.areContentsTheSame(newItem) 16 | } 17 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_edit.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/java/com/jintin/quickroute/db/QuickRouteDB.kt: -------------------------------------------------------------------------------- 1 | package com.jintin.quickroute.db 2 | 3 | import androidx.room.Database 4 | import androidx.room.RoomDatabase 5 | import androidx.room.TypeConverter 6 | import androidx.room.TypeConverters 7 | import com.jintin.quickroute.data.Action 8 | 9 | @Database(entities = [Action::class], version = 1, exportSchema = true) 10 | abstract class QuickRouteDB : RoomDatabase() { 11 | 12 | companion object { 13 | const val NAME = "QuickRouteDB" 14 | } 15 | 16 | abstract fun actionDao(): ActionDao 17 | } -------------------------------------------------------------------------------- /app/src/main/res/menu/extra.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 9 | 14 | -------------------------------------------------------------------------------- /app/src/main/java/com/jintin/quickroute/action/ActionListViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.jintin.quickroute.action 2 | 3 | import androidx.lifecycle.ViewModel 4 | import androidx.lifecycle.viewModelScope 5 | import com.jintin.quickroute.data.Action 6 | import com.jintin.quickroute.db.ActionDao 7 | import dagger.hilt.android.lifecycle.HiltViewModel 8 | import kotlinx.coroutines.launch 9 | import javax.inject.Inject 10 | 11 | @HiltViewModel 12 | class ActionListViewModel @Inject constructor( 13 | private val actionDao: ActionDao 14 | ) : ViewModel() { 15 | val liveData = actionDao.list() 16 | 17 | fun delete(data: Action) { 18 | viewModelScope.launch { 19 | actionDao.delete(data) 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 11 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # QuickRoute 2 | 3 | [![CircleCI](https://circleci.com/gh/Jintin/QuickRoute.svg?style=shield)](https://circleci.com/gh/Jintin/QuickRoute) 4 | 5 | Using Quick Settings Tile to navigate to Developer options page without click a lot of buttons. 6 | 7 | ## Preview 8 | 9 | ![](./README/preview.gif) 10 | 11 | ## Install 12 | 13 | 1. Can install from Google Play directly [here](https://play.google.com/store/apps/details?id=com.jintin.quickroute). 14 | 15 | 2. Pull the source and run on your own device. 16 | 17 | ## Contributing 18 | 19 | Bug reports and pull requests are welcome on GitHub at . 20 | 21 | ## License 22 | 23 | **QuickRoute** is released under Apache License 2.0. 24 | See [LICENSE](https://github.com/Jintin/QuickRoute/blob/master/LICENSE) for more details. -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | -------------------------------------------------------------------------------- /app/src/main/java/com/jintin/quickroute/AppModule.kt: -------------------------------------------------------------------------------- 1 | package com.jintin.quickroute 2 | 3 | import android.app.Application 4 | import androidx.room.Room 5 | import com.jintin.quickroute.db.QuickRouteDB 6 | import com.jintin.quickroute.db.ActionDao 7 | import dagger.Module 8 | import dagger.Provides 9 | import dagger.hilt.InstallIn 10 | import dagger.hilt.components.SingletonComponent 11 | import javax.inject.Singleton 12 | 13 | @InstallIn(SingletonComponent::class) 14 | @Module 15 | class AppModule { 16 | 17 | @Singleton 18 | @Provides 19 | fun provideDB(application: Application): QuickRouteDB { 20 | return Room.databaseBuilder( 21 | application, 22 | QuickRouteDB::class.java, 23 | QuickRouteDB.NAME 24 | ).build() 25 | } 26 | 27 | @Provides 28 | fun provideActionDao(db: QuickRouteDB): ActionDao { 29 | return db.actionDao() 30 | } 31 | } -------------------------------------------------------------------------------- /app/src/main/res/values-night/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | -------------------------------------------------------------------------------- /app/src/main/java/com/jintin/quickroute/db/ActionConverter.kt: -------------------------------------------------------------------------------- 1 | package com.jintin.quickroute.db 2 | 3 | import androidx.room.TypeConverter 4 | import com.jintin.quickroute.data.Extra 5 | import org.json.JSONArray 6 | 7 | class ActionConverter { 8 | 9 | @TypeConverter 10 | fun convert(list: List): String { 11 | val array = JSONArray() 12 | list.forEach { 13 | array.put("${it.name},${it.value},${it.type.ordinal}") 14 | } 15 | return array.toString() 16 | } 17 | 18 | @TypeConverter 19 | fun convert(string: String): List { 20 | val array = JSONArray(string) 21 | val list = mutableListOf() 22 | for (i in 0 until array.length()) { 23 | val values = array.optString(i, "") 24 | .split(",") 25 | list.add(Extra(values[0], values[1], Extra.Type.values()[values[2].toInt()])) 26 | } 27 | return list 28 | } 29 | 30 | } -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | 23 | -keepclassmembers class * implements androidx.viewbinding.ViewBinding { 24 | public static ** inflate(...); 25 | } -------------------------------------------------------------------------------- /app/src/main/java/com/jintin/quickroute/data/Action.kt: -------------------------------------------------------------------------------- 1 | package com.jintin.quickroute.data 2 | 3 | import android.os.Parcelable 4 | import androidx.room.Entity 5 | import androidx.room.PrimaryKey 6 | import androidx.room.TypeConverters 7 | import com.jintin.quickroute.base.ItemComparable 8 | import com.jintin.quickroute.db.ActionConverter 9 | import kotlinx.parcelize.Parcelize 10 | 11 | @TypeConverters(ActionConverter::class) 12 | @Parcelize 13 | @Entity(tableName = "action") 14 | data class Action( 15 | val name: String, 16 | val actName: String, 17 | val packageName: String, 18 | val extras: List = listOf(), 19 | @PrimaryKey(autoGenerate = true) 20 | var id: Int = 0, 21 | ) : ItemComparable, Parcelable { 22 | 23 | override fun areItemsTheSame(target: Action): Boolean { 24 | return id == target.id 25 | } 26 | 27 | override fun areContentsTheSame(target: Action): Boolean { 28 | return this == target 29 | } 30 | 31 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_dev_options.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 13 | 16 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/java/com/jintin/quickroute/data/IntentUtils.kt: -------------------------------------------------------------------------------- 1 | package com.jintin.quickroute.data 2 | 3 | import android.content.Intent 4 | 5 | fun Action.createIntent(): Intent { 6 | return Intent().setClassName(packageName, actName) 7 | .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) 8 | .also { intent -> 9 | extras.map { 10 | it.fillExtra(intent) 11 | } 12 | } 13 | } 14 | 15 | fun Extra.fillExtra(intent: Intent) { 16 | when (this.type) { 17 | Extra.Type.STRING -> intent.putExtra(name, value) 18 | Extra.Type.INT -> intent.putExtra(name, value.toInt()) 19 | Extra.Type.FLOAT -> intent.putExtra(name, value.toFloat()) 20 | Extra.Type.BYTE -> intent.putExtra(name, value.toByte()) 21 | Extra.Type.CHAR -> intent.putExtra(name, value.single()) 22 | Extra.Type.LONG -> intent.putExtra(name, value.toLong()) 23 | Extra.Type.SHORT -> intent.putExtra(name, value.toShort()) 24 | Extra.Type.DOUBLE -> intent.putExtra(name, value.toDouble()) 25 | Extra.Type.BOOLEAN -> intent.putExtra(name, value.toBoolean()) 26 | } 27 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app"s APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Kotlin code style for this project: "official" or "obsolete": 19 | kotlin.code.style=official -------------------------------------------------------------------------------- /app/src/main/res/layout/adapter_act.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 24 | -------------------------------------------------------------------------------- /app/src/main/java/com/jintin/quickroute/DevRouteService.kt: -------------------------------------------------------------------------------- 1 | package com.jintin.quickroute 2 | 3 | import android.content.Intent 4 | import android.content.Intent.FLAG_ACTIVITY_NEW_TASK 5 | import android.provider.Settings 6 | import android.service.quicksettings.TileService 7 | import android.widget.Toast 8 | 9 | class DevRouteService : TileService() { 10 | 11 | override fun onClick() { 12 | super.onClick() 13 | val adb = Settings.Secure.getInt( 14 | this.contentResolver, 15 | Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 16 | 0 17 | ) 18 | if (adb == 1) { 19 | startActivityAndCollapse(getIntent(Settings.ACTION_APPLICATION_DEVELOPMENT_SETTINGS)) 20 | } else { 21 | startActivityAndCollapse(getIntent(Settings.ACTION_SETTINGS)) 22 | Toast.makeText( 23 | applicationContext, 24 | R.string.dev_not_enable_msg, 25 | Toast.LENGTH_SHORT 26 | ).show() 27 | } 28 | } 29 | 30 | private fun getIntent(action: String): Intent { 31 | return Intent(action).apply { 32 | flags = FLAG_ACTIVITY_NEW_TASK 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /app/src/main/java/com/jintin/quickroute/select/ActListAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.jintin.quickroute.select 2 | 3 | import android.view.ViewGroup 4 | import androidx.recyclerview.widget.ListAdapter 5 | import androidx.recyclerview.widget.RecyclerView 6 | import com.jintin.bindingextension.toBinding 7 | import com.jintin.quickroute.base.ItemCallback 8 | import com.jintin.quickroute.data.Action 9 | import com.jintin.quickroute.databinding.AdapterActBinding 10 | 11 | class ActListAdapter( 12 | private val onSelectListener: (Action) -> Unit 13 | ) : ListAdapter(ItemCallback()) { 14 | 15 | inner class ViewHolder(private val binding: AdapterActBinding) : 16 | RecyclerView.ViewHolder(binding.root) { 17 | 18 | fun bind(data: Action) { 19 | binding.name.text = data.actName 20 | binding.root.setOnClickListener { 21 | onSelectListener.invoke(data) 22 | } 23 | } 24 | } 25 | 26 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = 27 | ViewHolder(parent.toBinding()) 28 | 29 | override fun onBindViewHolder(holder: ViewHolder, position: Int) { 30 | holder.bind(getItem(position)) 31 | } 32 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_app.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 17 | 18 | 26 | 27 | -------------------------------------------------------------------------------- /app/src/main/java/com/jintin/quickroute/base/RecyclerViewUtils.kt: -------------------------------------------------------------------------------- 1 | package com.jintin.quickroute.base 2 | 3 | import android.view.View 4 | import androidx.recyclerview.widget.RecyclerView 5 | 6 | 7 | fun RecyclerView.Adapter<*>.bindEmptyView(view: View) { 8 | 9 | fun updateEmptyViewVisibility() { 10 | view.visibility = if (itemCount == 0) { 11 | View.VISIBLE 12 | } else { 13 | View.GONE 14 | } 15 | } 16 | 17 | updateEmptyViewVisibility() 18 | 19 | registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() { 20 | override fun onChanged() { 21 | updateEmptyViewVisibility() 22 | } 23 | 24 | override fun onItemRangeChanged(positionStart: Int, itemCount: Int) { 25 | updateEmptyViewVisibility() 26 | } 27 | 28 | override fun onItemRangeInserted(positionStart: Int, itemCount: Int) { 29 | updateEmptyViewVisibility() 30 | } 31 | 32 | override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) { 33 | updateEmptyViewVisibility() 34 | } 35 | 36 | override fun onItemRangeMoved(fromPosition: Int, toPosition: Int, itemCount: Int) { 37 | updateEmptyViewVisibility() 38 | } 39 | }) 40 | 41 | } -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | QuickRoute 3 | Developer options 4 | "Need enable developer options first" 5 | Add 6 | Done 7 | Remove 8 | Apply 9 | Extra Name 10 | Type 11 | Value 12 | Action List 13 | Select App 14 | Select Activity 15 | Modify Extra 16 | Tutorial 17 | Delete 18 | Modify 19 | There\'s no action to launch yet, click add to create one! 20 | There\'s no Activity yet 21 | There\'s no Extra yet 22 | Not able to launch this Activity 23 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_act.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 17 | 18 | 28 | 29 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_extra.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 17 | 18 | 28 | 29 | -------------------------------------------------------------------------------- /app/src/main/java/com/jintin/quickroute/extra/ExtraListAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.jintin.quickroute.extra 2 | 3 | import android.annotation.SuppressLint 4 | import android.view.ViewGroup 5 | import androidx.recyclerview.widget.ListAdapter 6 | import androidx.recyclerview.widget.RecyclerView 7 | import com.jintin.bindingextension.toBinding 8 | import com.jintin.quickroute.base.ItemCallback 9 | import com.jintin.quickroute.data.Extra 10 | import com.jintin.quickroute.databinding.AdapterExtraBinding 11 | 12 | class ExtraListAdapter(private val onSelectListener: (Extra) -> Unit) : 13 | ListAdapter(ItemCallback()) { 14 | 15 | inner class ViewHolder(private val binding: AdapterExtraBinding) : 16 | RecyclerView.ViewHolder(binding.root) { 17 | 18 | @SuppressLint("SetTextI18n") 19 | fun bind(data: Extra) { 20 | binding.name.text = data.name 21 | binding.value.text = data.value 22 | binding.type.text = "${data.type.label}:" 23 | binding.root.setOnClickListener { 24 | onSelectListener.invoke(data) 25 | } 26 | } 27 | } 28 | 29 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = 30 | ViewHolder(parent.toBinding()) 31 | 32 | override fun onBindViewHolder(holder: ViewHolder, position: Int) { 33 | holder.bind(getItem(position)) 34 | } 35 | } -------------------------------------------------------------------------------- /app/src/main/java/com/jintin/quickroute/extra/EditDialogViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.jintin.quickroute.extra 2 | 3 | import androidx.lifecycle.LiveData 4 | import androidx.lifecycle.MutableLiveData 5 | import androidx.lifecycle.ViewModel 6 | import com.jintin.quickroute.data.Extra 7 | import javax.inject.Inject 8 | 9 | class EditDialogViewModel @Inject constructor() : ViewModel() { 10 | 11 | private val _validateLiveData = MutableLiveData() 12 | val validateLiveData: LiveData 13 | get() = _validateLiveData 14 | 15 | private var name: String? = null 16 | private var type: Extra.Type? = null 17 | private var value: String? = null 18 | 19 | fun setExtra(extra: Extra?) { 20 | name = extra?.name 21 | type = extra?.type 22 | value = extra?.value 23 | validate() 24 | } 25 | 26 | fun setName(value: String) { 27 | this.name = value 28 | validate() 29 | } 30 | 31 | fun setType(value: String) { 32 | this.type = Extra.Type.getType(value) 33 | validate() 34 | } 35 | 36 | fun setValue(value: String) { 37 | this.value = value 38 | validate() 39 | } 40 | 41 | fun validate() { 42 | if (name == null || name?.length == 0) { 43 | _validateLiveData.value = false 44 | return 45 | } 46 | _validateLiveData.value = type?.isValid(value) ?: false 47 | } 48 | } -------------------------------------------------------------------------------- /.idea/jarRepositories.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 14 | 15 | 19 | 20 | 24 | 25 | 29 | 30 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/java/com/jintin/quickroute/select/AppListViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.jintin.quickroute.select 2 | 3 | import android.app.Application 4 | import android.content.pm.PackageManager 5 | import androidx.lifecycle.AndroidViewModel 6 | import androidx.lifecycle.LiveData 7 | import androidx.lifecycle.MutableLiveData 8 | import androidx.lifecycle.viewModelScope 9 | import com.jintin.quickroute.data.AppInfo 10 | import dagger.hilt.android.lifecycle.HiltViewModel 11 | import kotlinx.coroutines.* 12 | import javax.inject.Inject 13 | 14 | @HiltViewModel 15 | class AppListViewModel @Inject constructor(app: Application) : AndroidViewModel(app) { 16 | 17 | private val _liveDate = MutableLiveData>() 18 | val liveData: LiveData> 19 | get() = _liveDate 20 | 21 | init { 22 | viewModelScope.launch { 23 | _liveDate.value = getList(app.packageManager) 24 | } 25 | } 26 | 27 | private suspend fun getList(packageManager: PackageManager) = withContext(Dispatchers.IO) { 28 | packageManager.getInstalledApplications(PackageManager.GET_META_DATA) 29 | .filter { packageManager.getLaunchIntentForPackage(it.packageName) != null } 30 | .parallelMap { 31 | AppInfo( 32 | packageManager.getApplicationLabel(it).toString(), 33 | it.packageName, 34 | ) 35 | } 36 | } 37 | 38 | 39 | private suspend fun Iterable.parallelMap(f: suspend (A) -> B): List = 40 | coroutineScope { 41 | map { async { f(it) } }.awaitAll() 42 | } 43 | } -------------------------------------------------------------------------------- /app/src/main/java/com/jintin/quickroute/select/AppListActivity.kt: -------------------------------------------------------------------------------- 1 | package com.jintin.quickroute.select 2 | 3 | import android.content.Context 4 | import android.content.Intent 5 | import android.os.Bundle 6 | import androidx.activity.viewModels 7 | import androidx.lifecycle.lifecycleScope 8 | import androidx.recyclerview.widget.LinearLayoutManager 9 | import com.jintin.bindingextension.BindingActivity 10 | import com.jintin.quickroute.base.bindEmptyView 11 | import com.jintin.quickroute.data.AppInfo 12 | import com.jintin.quickroute.databinding.ActivityAppBinding 13 | import dagger.hilt.android.AndroidEntryPoint 14 | 15 | @AndroidEntryPoint 16 | class AppListActivity : BindingActivity() { 17 | 18 | companion object { 19 | fun start(context: Context) { 20 | context.startActivity(Intent(context, AppListActivity::class.java)) 21 | } 22 | } 23 | 24 | private val viewModel by viewModels() 25 | 26 | override fun onCreate(savedInstanceState: Bundle?) { 27 | super.onCreate(savedInstanceState) 28 | 29 | val adapter = AppListAdapter(packageManager, lifecycleScope, ::onSelect) 30 | adapter.bindEmptyView(binding.progressBar) 31 | binding.recyclerView.layoutManager = 32 | LinearLayoutManager(baseContext, LinearLayoutManager.VERTICAL, false) 33 | binding.recyclerView.adapter = adapter 34 | viewModel.liveData.observe(this) { 35 | adapter.submitList(it) 36 | } 37 | } 38 | 39 | private fun onSelect(info: AppInfo) { 40 | ActListActivity.start(this, info.name, info.packageName) 41 | } 42 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/adapter_app.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 20 | 21 | 35 | -------------------------------------------------------------------------------- /app/src/main/java/com/jintin/quickroute/select/ActListViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.jintin.quickroute.select 2 | 3 | import android.app.Application 4 | import android.content.pm.PackageManager 5 | import androidx.lifecycle.AndroidViewModel 6 | import androidx.lifecycle.LiveData 7 | import androidx.lifecycle.MutableLiveData 8 | import androidx.lifecycle.viewModelScope 9 | import com.jintin.quickroute.data.Action 10 | import com.jintin.quickroute.db.ActionDao 11 | import dagger.hilt.android.lifecycle.HiltViewModel 12 | import kotlinx.coroutines.launch 13 | import javax.inject.Inject 14 | 15 | @HiltViewModel 16 | class ActListViewModel @Inject constructor( 17 | app: Application, 18 | private val actionDao: ActionDao 19 | ) : AndroidViewModel(app) { 20 | 21 | private val _liveData = MutableLiveData>() 22 | val liveData: LiveData> 23 | get() = _liveData 24 | 25 | fun getList(appName: String, packageName: String) { 26 | try { 27 | _liveData.value = getApplication() 28 | .packageManager 29 | .getPackageInfo( 30 | packageName, 31 | PackageManager.GET_ACTIVITIES 32 | ).activities 33 | .filter { it.exported } 34 | .map { 35 | Action(appName, it.name, it.packageName) 36 | } 37 | } catch (e: PackageManager.NameNotFoundException) { 38 | e.printStackTrace() 39 | } 40 | } 41 | 42 | fun add(action: Action) { 43 | viewModelScope.launch { 44 | actionDao.insert(action) 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /app/src/main/java/com/jintin/quickroute/select/AppListAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.jintin.quickroute.select 2 | 3 | import android.content.pm.PackageManager 4 | import android.view.ViewGroup 5 | import androidx.recyclerview.widget.ListAdapter 6 | import androidx.recyclerview.widget.RecyclerView 7 | import com.jintin.bindingextension.toBinding 8 | import com.jintin.quickroute.base.ItemCallback 9 | import com.jintin.quickroute.base.loadImage 10 | import com.jintin.quickroute.data.AppInfo 11 | import com.jintin.quickroute.databinding.AdapterAppBinding 12 | import kotlinx.coroutines.CoroutineScope 13 | import kotlinx.coroutines.Job 14 | import kotlinx.coroutines.launch 15 | 16 | class AppListAdapter( 17 | private val packageManager: PackageManager, 18 | private val lifecycleScope: CoroutineScope, 19 | private val onSelectListener: (AppInfo) -> Unit 20 | ) : ListAdapter(ItemCallback()) { 21 | 22 | inner class ViewHolder(private val binding: AdapterAppBinding) : 23 | RecyclerView.ViewHolder(binding.root) { 24 | private var job: Job? = null 25 | 26 | fun bind(data: AppInfo) { 27 | job?.cancel() 28 | job = lifecycleScope.launch { 29 | packageManager.loadImage(binding.icon, data.packageName) 30 | } 31 | binding.name.text = data.name 32 | binding.root.setOnClickListener { onSelectListener.invoke(data) } 33 | } 34 | } 35 | 36 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = 37 | ViewHolder(parent.toBinding()) 38 | 39 | override fun onBindViewHolder(holder: ViewHolder, position: Int) { 40 | holder.bind(getItem(position)) 41 | } 42 | } -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # Use the latest 2.1 version of CircleCI pipeline process engine. 2 | # See: https://circleci.com/docs/2.0/configuration-reference 3 | version: 2.1 4 | 5 | # Orbs are reusable packages of CircleCI configuration that you may share across projects, enabling you to create encapsulated, parameterized commands, jobs, and executors that can be used across multiple projects. 6 | orbs: 7 | android: circleci/android@1.0.3 8 | 9 | jobs: 10 | # Below is the definition of your job to build and test your app, you can rename and customize it as you want. 11 | build-and-test: 12 | # These next lines define the Android machine image executor: https://circleci.com/docs/2.0/executor-types/ 13 | executor: 14 | name: android/android-machine 15 | 16 | steps: 17 | # Checkout the code as the first step. 18 | - checkout 19 | 20 | # The next step will run the unit tests 21 | - android/run-tests: 22 | test-command: ./gradlew lint testDebug --continue 23 | 24 | # Then start the emulator and run the Instrumentation tests! 25 | - android/start-emulator-and-run-tests: 26 | test-command: ./gradlew connectedDebugAndroidTest 27 | system-image: system-images;android-25;google_apis;x86 28 | 29 | # And finally run the release build 30 | - run: 31 | name: Assemble release build 32 | command: | 33 | ./gradlew assembleRelease 34 | 35 | workflows: 36 | # Below is the definition of your workflow. 37 | # Inside the workflow, you provide the jobs you want to run, e.g this workflow runs the build-and-test job above. 38 | # CircleCI will run this workflow on every commit. 39 | # For more details on extending your workflow, see the configuration docs: https://circleci.com/docs/2.0/configuration-reference/#workflows 40 | sample: 41 | jobs: 42 | - build-and-test 43 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_action_sheet.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 22 | 23 | 38 | -------------------------------------------------------------------------------- /app/schemas/com.jintin.quickroute.db.QuickRouteDB/1.json: -------------------------------------------------------------------------------- 1 | { 2 | "formatVersion": 1, 3 | "database": { 4 | "version": 1, 5 | "identityHash": "4cf40248155d926465d4f51835706998", 6 | "entities": [ 7 | { 8 | "tableName": "action", 9 | "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`name` TEXT NOT NULL, `actName` TEXT NOT NULL, `packageName` TEXT NOT NULL, `extras` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", 10 | "fields": [ 11 | { 12 | "fieldPath": "name", 13 | "columnName": "name", 14 | "affinity": "TEXT", 15 | "notNull": true 16 | }, 17 | { 18 | "fieldPath": "actName", 19 | "columnName": "actName", 20 | "affinity": "TEXT", 21 | "notNull": true 22 | }, 23 | { 24 | "fieldPath": "packageName", 25 | "columnName": "packageName", 26 | "affinity": "TEXT", 27 | "notNull": true 28 | }, 29 | { 30 | "fieldPath": "extras", 31 | "columnName": "extras", 32 | "affinity": "TEXT", 33 | "notNull": true 34 | }, 35 | { 36 | "fieldPath": "id", 37 | "columnName": "id", 38 | "affinity": "INTEGER", 39 | "notNull": true 40 | } 41 | ], 42 | "primaryKey": { 43 | "columnNames": [ 44 | "id" 45 | ], 46 | "autoGenerate": true 47 | }, 48 | "indices": [], 49 | "foreignKeys": [] 50 | } 51 | ], 52 | "views": [], 53 | "setupQueries": [ 54 | "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", 55 | "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '4cf40248155d926465d4f51835706998')" 56 | ] 57 | } 58 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/adapter_extra.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 22 | 23 | 31 | 32 | 43 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_action.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 17 | 18 | 29 | 30 | 35 | 36 |