• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.wm.shell.common
17 
18 import android.annotation.UserIdInt
19 import android.app.PendingIntent
20 import android.content.ComponentName
21 import android.content.Context
22 import android.content.pm.LauncherApps
23 import android.content.pm.PackageManager
24 import android.content.pm.PackageManager.Property
25 import android.os.UserHandle
26 import android.view.WindowManager.PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI
27 import com.android.internal.protolog.ProtoLog
28 import com.android.wm.shell.R
29 import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL
30 import com.android.wm.shell.sysui.ShellCommandHandler
31 import com.android.wm.shell.sysui.ShellInit
32 import java.io.PrintWriter
33 import java.util.Arrays
34 
35 /**
36  * Helper for multi-instance related checks.
37  */
38 class MultiInstanceHelper @JvmOverloads constructor(
39     private val context: Context,
40     private val packageManager: PackageManager,
41     private val staticAppsSupportingMultiInstance: Array<String> = context.resources
42             .getStringArray(R.array.config_appsSupportMultiInstancesSplit),
43     shellInit: ShellInit,
44     private val shellCommandHandler: ShellCommandHandler,
45     private val supportsMultiInstanceProperty: Boolean
46 ) : ShellCommandHandler.ShellCommandActionHandler {
47 
48     init {
49         shellInit.addInitCallback(this::onInit, this)
50     }
51 
52     private fun onInit() {
53         shellCommandHandler.addCommandCallback("multi-instance", this, this)
54     }
55 
56     /**
57      * Returns whether a specific component desires to be launched in multiple instances.
58      */
59     fun supportsMultiInstanceSplit(componentName: ComponentName?, @UserIdInt userId: Int): Boolean {
60         if (componentName == null || componentName.packageName == null) {
61             // TODO(b/262864589): Handle empty component case
62             return false
63         }
64 
65         // Check the pre-defined allow list
66         val packageName = componentName.packageName
67         for (pkg in staticAppsSupportingMultiInstance) {
68             if (pkg == packageName) {
69                 ProtoLog.v(WM_SHELL, "application=%s in allowlist supports multi-instance",
70                     packageName)
71                 return true
72             }
73         }
74 
75         if (!supportsMultiInstanceProperty) {
76             // If not checking the multi-instance properties, then return early
77             return false
78         }
79 
80         // Check the activity property first
81         try {
82             val activityProp = packageManager.getPropertyAsUser(
83                 PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI, componentName.packageName,
84                 componentName.className, userId)
85             // If the above call doesn't throw a NameNotFoundException, then the activity property
86             // should override the application property value
87             if (activityProp.isBoolean) {
88                 ProtoLog.v(WM_SHELL, "activity=%s supports multi-instance", componentName)
89                 return activityProp.boolean
90             } else {
91                 ProtoLog.w(WM_SHELL, "Warning: property=%s for activity=%s has non-bool type=%d",
92                     PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI, packageName, activityProp.type)
93             }
94         } catch (nnfe: PackageManager.NameNotFoundException) {
95             // Not specified in the activity, fall through
96         }
97 
98         // Check the application property otherwise
99         try {
100             val appProp = packageManager.getPropertyAsUser(
101                 PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI, packageName, null /* className */,
102                 userId)
103             if (appProp.isBoolean) {
104                 ProtoLog.v(WM_SHELL, "application=%s supports multi-instance", packageName)
105                 return appProp.boolean
106             } else {
107                 ProtoLog.w(WM_SHELL,
108                     "Warning: property=%s for application=%s has non-bool type=%d",
109                     PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI, packageName, appProp.type)
110             }
111         } catch (nnfe: PackageManager.NameNotFoundException) {
112             // Not specified in either application or activity
113         }
114         return false
115     }
116 
117     override fun onShellCommand(args: Array<out String>?, pw: PrintWriter?): Boolean {
118         if (pw == null || args == null || args.isEmpty()) {
119             return false
120         }
121         when (args[0]) {
122             "list" -> return dumpSupportedApps(pw)
123         }
124         return false
125     }
126 
127     override fun printShellCommandHelp(pw: PrintWriter, prefix: String) {
128         pw.println("${prefix}list")
129         pw.println("$prefix   Lists all the packages that support the multiinstance property")
130     }
131 
132     /**
133      * Dumps the static allowlist and list of apps that have the declared property in the manifest.
134      */
135     private fun dumpSupportedApps(pw: PrintWriter): Boolean {
136         pw.println("Static allow list (for all users):")
137         staticAppsSupportingMultiInstance.forEach { pkg ->
138             pw.println("  $pkg")
139         }
140 
141         // TODO(b/391693747): Dump this per-user once PM allows us to query properties
142         //                    for non-calling users
143         val apps = packageManager.queryApplicationProperty(
144             PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI)
145         val activities = packageManager.queryActivityProperty(
146             PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI)
147         val appsWithProperty = (apps + activities)
148             .sortedWith(object : Comparator<Property?> {
149                 override fun compare(o1: Property?, o2: Property?): Int {
150                     if (o1?.packageName != o2?.packageName) {
151                         return o1?.packageName!!.compareTo(o2?.packageName!!)
152                     } else {
153                         if (o1?.className != null) {
154                             return o1.className!!.compareTo(o2?.className!!)
155                         } else if (o2?.className != null) {
156                             return -o2.className!!.compareTo(o1?.className!!)
157                         }
158                         return 0
159                     }
160                 }
161             })
162         if (appsWithProperty.isNotEmpty()) {
163             pw.println("Apps (User ${context.userId}):")
164             appsWithProperty.forEach { prop ->
165                 if (prop.isBoolean && prop.boolean) {
166                     if (prop.className != null) {
167                         pw.println("  ${prop.packageName}/${prop.className}")
168                     } else {
169                         pw.println("  ${prop.packageName}")
170                     }
171                 }
172             }
173         }
174         return true
175     }
176 
177     companion object {
178         /** Returns the component from a PendingIntent  */
179         @JvmStatic
180         fun getComponent(pendingIntent: PendingIntent?): ComponentName? {
181             return pendingIntent?.intent?.component
182         }
183 
184         /** Returns the component from a shortcut  */
185         @JvmStatic
186         fun getShortcutComponent(packageName: String, shortcutId: String,
187                 user: UserHandle, launcherApps: LauncherApps): ComponentName? {
188             val query = LauncherApps.ShortcutQuery()
189             query.setPackage(packageName)
190             query.setShortcutIds(Arrays.asList(shortcutId))
191             query.setQueryFlags(LauncherApps.ShortcutQuery.FLAG_MATCH_ALL_KINDS_WITH_ALL_PINNED)
192             val shortcuts = launcherApps.getShortcuts(query, user)
193             val info = if (shortcuts != null && shortcuts.size > 0) shortcuts[0] else null
194             return info?.activity
195         }
196 
197         /** Returns true if package names and user ids match.  */
198         @JvmStatic
199         fun samePackage(packageName1: String?, packageName2: String?,
200                 userId1: Int, userId2: Int): Boolean {
201             return (packageName1 != null && packageName1 == packageName2) && (userId1 == userId2)
202         }
203     }
204 }
205