11package com.oasisfeng.nevo.decorators.wechat
22
33import android.annotation.SuppressLint
4- import android.content.ComponentName
5- import android.content.Context
6- import android.content.Intent
7- import android.content.LocusId
4+ import android.content.*
5+ import android.content.pm.LauncherApps
86import android.content.pm.PackageManager
7+ import android.content.pm.PackageManager.GET_ACTIVITIES
98import android.content.pm.ShortcutInfo
109import android.content.pm.ShortcutManager
10+ import android.graphics.drawable.Icon
11+ import android.graphics.drawable.Icon.TYPE_RESOURCE
1112import android.os.Build.VERSION.SDK_INT
1213import android.os.Build.VERSION_CODES.N
1314import android.os.Build.VERSION_CODES.N_MR1
1415import android.os.Build.VERSION_CODES.Q
1516import android.os.Process
1617import android.os.UserHandle
1718import android.os.UserManager
19+ import android.util.ArrayMap
1820import android.util.Log
1921import android.util.LruCache
2022import androidx.annotation.RequiresApi
23+ import androidx.core.content.getSystemService
24+ import androidx.core.graphics.drawable.IconCompat
2125import com.oasisfeng.nevo.decorators.wechat.ConversationManager.Conversation
2226import com.oasisfeng.nevo.decorators.wechat.WeChatDecorator.AGENT_PACKAGE
2327import com.oasisfeng.nevo.decorators.wechat.WeChatDecorator.TAG
@@ -30,9 +34,8 @@ import java.lang.reflect.Method
3034 fun buildShortcutId (key : String ) = " C:$key "
3135 }
3236
33- private fun updateShortcut (conversation : Conversation , profile : UserHandle ): Boolean {
34- val key = conversation.key ? : return false .also { Log .i(TAG , " Postpone shortcut update until conversation key is fetched." ) }
35- val agentContext = createAgentContext(profile) ? : return false
37+ /* * @return true if shortcut is ready */
38+ private fun updateShortcut (id : String , conversation : Conversation , agentContext : Context ): Boolean {
3639 if (SDK_INT >= N && agentContext.getSystemService(UserManager ::class .java)?.isUserUnlocked == false ) return false // Shortcuts cannot be changed if user is locked.
3740
3841 val activity = agentContext.packageManager.resolveActivity(Intent (Intent .ACTION_MAIN ) // Use agent context to resolve in proper user.
@@ -41,25 +44,35 @@ import java.lang.reflect.Method
4144
4245 val sm = agentContext.getShortcutManager() ? : return false
4346 if (sm.isRateLimitingActive)
44- return false .also { Log .w(TAG , " Due to rate limit, shortcut is not updated: $key " ) }
47+ return false .also { Log .w(TAG , " Due to rate limit, shortcut is not updated: $id " ) }
4548
4649 val shortcuts = sm.dynamicShortcuts.apply { sortBy { it.rank }}; val count = shortcuts.size
47- shortcuts.forEach { shortcut -> if (buildShortcutId(key) == shortcut.id) return true }
4850 if (count >= sm.maxShortcutCountPerActivity - sm.manifestShortcuts.size)
49- sm.removeDynamicShortcuts(listOf (shortcuts.removeAt(0 ).id))
51+ sm.removeDynamicShortcuts(listOf (shortcuts.removeAt(0 ).id. also { Log .i( TAG , " Evict excess shortcut: $it " ) } ))
5052
51- val intent = Intent ().setComponent( ComponentName ( WECHAT_PACKAGE , " com.tencent.mm.ui.LauncherUI" ) )
52- .putExtra(" Main_User" , key).putExtra(@Suppress(" SpellCheckingInspection" ) " Intro_Is_Muti_Talker" , false )
53+ val intent = if (conversation.ext != null ) Intent ().setClassName( WECHAT_PACKAGE , " com.tencent.mm.ui.LauncherUI" )
54+ .putExtra(" Main_User" , conversation. key).putExtra(@Suppress(" SpellCheckingInspection" ) " Intro_Is_Muti_Talker" , false )
5355 .addFlags(Intent .FLAG_ACTIVITY_NO_HISTORY )
54- val shortcut = ShortcutInfo .Builder (agentContext, buildShortcutId(key)).setActivity(ComponentName (AGENT_PACKAGE , activity))
56+ else {
57+ val bubbleActivity = (mAgentBubbleActivity
58+ ? : try { context.packageManager.getPackageInfo(AGENT_PACKAGE , GET_ACTIVITIES ).activities
59+ .firstOrNull { it.enabled && it.flags.and (FLAG_ALLOW_EMBEDDED ) != 0 }?.name ? : " " }
60+ catch (e: PackageManager .NameNotFoundException ) { " " }.also { mAgentBubbleActivity = it }) // "" to indicate N/A
61+ if (bubbleActivity.isNotEmpty()) {
62+ Intent (Intent .ACTION_VIEW_LOCUS ).putExtra(Intent .EXTRA_LOCUS_ID , id).setClassName(AGENT_PACKAGE , bubbleActivity)
63+ } else Intent ().setClassName(AGENT_PACKAGE , activity)
64+ }
65+
66+ val shortcut = ShortcutInfo .Builder (agentContext, id).setActivity(ComponentName (AGENT_PACKAGE , activity))
5567 .setShortLabel(conversation.title).setRank(if (conversation.isGroupChat) 1 else 0 ) // Always keep last direct message conversation on top.
5668 .setIntent(intent.apply { if (action == null ) action = Intent .ACTION_MAIN })
5769 .setCategories(setOf (ShortcutInfo .SHORTCUT_CATEGORY_CONVERSATION )).apply {
58- if (conversation.icon != null ) setIcon(IconHelper .convertToAdaptiveIcon (context, sm, conversation.icon ))
70+ if (conversation.icon != null ) setIcon(conversation.icon.toLocalAdaptiveIcon (context, sm))
5971 if (SDK_INT >= Q ) @SuppressLint(" RestrictedApi" ) {
60- setLongLived(true ).setLocusId(LocusId (key))
61- if (! conversation.isGroupChat) setPerson(conversation.sender().build().toAndroidPerson()) }}
62- return if (sm.addDynamicShortcuts(listOf (shortcut.build()))) true .also { Log .i(TAG , " Shortcut updated for $key " ) }
72+ setLongLived(true ).setLocusId(LocusId (id))
73+ if (! conversation.isGroupChat) setPerson(conversation.sender().build().toAndroidPerson()) }}.build()
74+ if (BuildConfig .DEBUG ) { Log .i(TAG , " Updating shortcut \" ${shortcut.id} \" : ${shortcut.intent.toString()} " ) }
75+ return if (sm.addDynamicShortcuts(listOf (shortcut))) true .also { Log .i(TAG , " Shortcut updated: $id " ) }
6376 else false .also { Log .e(TAG , " Unexpected rate limit." ) }
6477 }
6578
@@ -70,20 +83,50 @@ import java.lang.reflect.Method
7083 catch (e: PackageManager .NameNotFoundException ) { null }
7184 catch (e: RuntimeException ) { null .also { Log .e(TAG , " Error creating context for agent in user ${profile.hashCode()} " , e) }}
7285
73- fun updateShortcutIfNeeded (conversation : Conversation , profile : UserHandle ) {
74- val key = conversation.key
75- if (SDK_INT < N_MR1 || key == null || ! conversation.isChat || conversation.isBotMessage) return
76- if (mDynamicShortcutContacts.get(key) == null ) {
77- try { if (updateShortcut(conversation, profile)) mDynamicShortcutContacts.put(key, Unit ) }
78- catch (e: RuntimeException ) { Log .e(TAG , " Error publishing shortcut for $key " , e) }}
86+ /* * @return whether shortcut is ready */
87+ @RequiresApi(N_MR1 ) fun updateShortcutIfNeeded (id : String , conversation : Conversation , profile : UserHandle ): Boolean {
88+ if (! conversation.isChat || conversation.isBotMessage) return false
89+ val agentContext = mAgentContextByProfile[profile] ? : return false
90+ if (mDynamicShortcutContacts.get(id) != null ) return true
91+ try { if (updateShortcut(id, conversation, agentContext))
92+ return true .also { if (conversation.icon.type != TYPE_RESOURCE ) mDynamicShortcutContacts.put(id, Unit ) }} // If no large icon, wait for the next update
93+ catch (e: RuntimeException ) { Log .e(TAG , " Error publishing shortcut: $id " , e) }
94+ return false
7995 }
8096
8197 private fun Context.getShortcutManager () = getSystemService(ShortcutManager ::class .java)
8298
99+ private var mAgentBubbleActivity: String? = null
100+ private val mPackageEventReceiver = object : LauncherApps .Callback () {
101+
102+ private fun update (pkg : String , user : UserHandle ) {
103+ if (pkg == AGENT_PACKAGE ) mAgentContextByProfile[user] = createAgentContext(user)
104+ }
105+
106+ override fun onPackageRemoved (pkg : String , user : UserHandle ) { update(pkg, user) }
107+ override fun onPackageAdded (pkg : String , user : UserHandle ) { update(pkg, user) }
108+ override fun onPackageChanged (pkg : String , user : UserHandle ) { update(pkg, user) }
109+ override fun onPackagesAvailable (pkgs : Array <out String >, user : UserHandle , replacing : Boolean ) { pkgs.forEach { update(it, user) }}
110+ override fun onPackagesUnavailable (pkgs : Array <out String >, user : UserHandle , replacing : Boolean ) { pkgs.forEach { update(it, user) }}
111+ }
112+
83113 /* * Local mark to reduce repeated shortcut updates */
84- private val mDynamicShortcutContacts = LruCache <String , Unit >(3 ) // Do not rely on maxShortcutCountPerActivity(), as most launcher only display top 4 shortcuts (including manifest shortcuts)
114+ private val mDynamicShortcutContacts = LruCache <String / * shortcut ID * / , Unit >(3 ) // Do not rely on maxShortcutCountPerActivity(), as most launcher only display top 4 shortcuts (including manifest shortcuts)
85115
86- // private val mShortcutIdsByProfile = SparseArray<Set<Int>>()
87116 private val mMethodCreatePackageContextAsUser: Method ? by lazy {
88117 try { Context ::class .java.getMethod(" createPackageContextAsUser" ) } catch (e: ReflectiveOperationException ) { null }}
118+ private val mAgentContextByProfile = ArrayMap <UserHandle , Context ?>()
119+
120+ init {
121+ context.getSystemService<LauncherApps >()?.registerCallback(mPackageEventReceiver)
122+ context.getSystemService<UserManager >()?.userProfiles?.forEach {
123+ mAgentContextByProfile[it] = createAgentContext(it) }
124+ }
125+
126+ fun close () {
127+ context.getSystemService<LauncherApps >()?.unregisterCallback(mPackageEventReceiver)
128+ mAgentContextByProfile.clear()
129+ }
89130}
131+
132+ const val FLAG_ALLOW_EMBEDDED = - 0x80000000
0 commit comments