lawnchair
feat: add open in store shortcut (#6461)

需求简介

需求很简单,就是你长按一个app,然后如果它不是系统应用,有来源,就显示来源,然可以跳转到对应的来源去

需求拆解

可以拆成几个部分,就是长按这个app,filter,跳转intent

shortcutMenu

然后长按这里

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
kotlin
override fun getSupportedShortcuts(container: Int): Stream<SystemShortcut.Factory<*>> = Stream.concat(
super.getSupportedShortcuts(container), // AOSP 原有的快捷方式(App Info 等)
Stream.concat(
Stream.of(LawnchairShortcut.UNINSTALL, LawnchairShortcut.CUSTOMIZE, LawnchairShortcut.OPEN_IN_STORE),
if (LawnchairApp.isRecentsEnabled) Stream.of(LawnchairShortcut.PAUSE_APPS) else Stream.empty(),
),
)


Stream.concat 就是把多个流拼接成一个流。这里做的事情是:

最终的快捷方式列表 = AOSP 默认的 + Lawnchair 自己加的
├── UNINSTALL(卸载)
├── CUSTOMIZE(自定义图标)
├── OPEN_IN_STORE(在商店打开)← 新加的
└── PAUSE_APPS(暂停应用,仅 root 有)

这里的流说实话没太懂,后面补习一下吧。TODO:补习流
然后具体来实现,就是你这个shortcutMenu需要添加一个类似于其他实现的自定义的一个东西
这里的工厂上一个Single Abstract Method接口,这里就kotlin语法糖,直接Factory{lambda}可以实现

1
2
3
4
// AOSP Launcher3 里的定义(Java)
public static class Factory<T extends ActivityContext> {
public SystemShortcut<T> getShortcut(T activity, ItemInfo itemInfo, View originalView);
}

具体代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
val OPEN_IN_STORE =
SystemShortcut.Factory { activity: ActivityContext, itemInfo: ItemInfo, originalView: View ->
if (itemInfo.itemType != ITEM_TYPE_APPLICATION) return@Factory null
val packageName = itemInfo.targetComponent?.packageName ?: return@Factory null
val context = activity.asContext()
val installer = PackageManagerHelper.INSTANCE.get(context)
.getAppInstallerPackage(packageName) ?: return@Factory null
if (installer == "com.google.android.packageinstaller" ||
installer == "com.android.packageinstaller"
) {
return@Factory null
}
OpenInStore(activity, itemInfo, originalView, packageName, installer)
}

然后这里就是看是否合法,不合法只退出factory这层,kotlin的return会直接退出函数,如果不指定层级的话。
然后这里实际上就是希望通过系统的getAppInstallerPackage方法来拿到最开始install的包名

具体shortcut

需要实现点击和跳转

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class OpenInStore(
target: ActivityContext,
itemInfo: ItemInfo,
originalView: View,
private val packageName: String,
private val installerPackage: String,
) : SystemShortcut<ActivityContext>(
R.drawable.ic_open_in_store,
R.string.open_in_store_drop_target_label,
target,
itemInfo,
originalView,
) {
override fun onClick(v: View) {
dismissTaskMenuView()
val intent = buildIntent() ?: return
mTarget.startActivitySafely(v, intent, mItemInfo)
}

private fun buildIntent(): Intent? {
val uri = when (installerPackage) {
"com.android.vending",
"org.gdroid.gdroid",
"com.aurora.store",
-> "market://details?id=$packageName"

"org.fdroid.fdroid" -> "https://f-droid.org/packages/$packageName/"

"com.github.librecaptcha.apps.fdroidclient",
"com.looker.droidify",
-> "droidify://details?id=$packageName"

else -> "market://details?id=$packageName"
}
return Intent(Intent.ACTION_VIEW, Uri.parse(uri))
}
}

直接返回继承SystemShortcut的匿名类也行,只需要重写这俩方法,不写名字也行,不过匿名感觉太丑了,这里符合语义就搞了个类。然后就是非法判断加intent构建,intent就根据不同包名返回需要跳到的位置即可。