1 /*
2  * 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.app.PendingIntent
19 import android.content.ComponentName
20 import android.content.Context
21 import android.content.pm.LauncherApps
22 import android.content.pm.PackageManager
23 import android.os.UserHandle
24 import android.view.WindowManager.PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI
25 import com.android.internal.annotations.VisibleForTesting
26 import com.android.wm.shell.R
27 import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL
28 import com.android.wm.shell.util.KtProtoLog
29 import java.util.Arrays
30 
31 /**
32  * Helper for multi-instance related checks.
33  */
34 class MultiInstanceHelper @JvmOverloads constructor(
35     private val context: Context,
36     private val packageManager: PackageManager,
37     private val staticAppsSupportingMultiInstance: Array<String> = context.resources
38             .getStringArray(R.array.config_appsSupportMultiInstancesSplit),
39     private val supportsMultiInstanceProperty: Boolean) {
40 
41     /**
42      * Returns whether a specific component desires to be launched in multiple instances.
43      */
44     @VisibleForTesting
supportsMultiInstanceSplitnull45     fun supportsMultiInstanceSplit(componentName: ComponentName?): Boolean {
46         if (componentName == null || componentName.packageName == null) {
47             // TODO(b/262864589): Handle empty component case
48             return false
49         }
50 
51         // Check the pre-defined allow list
52         val packageName = componentName.packageName
53         for (pkg in staticAppsSupportingMultiInstance) {
54             if (pkg == packageName) {
55                 KtProtoLog.v(WM_SHELL, "application=%s in allowlist supports multi-instance",
56                     packageName)
57                 return true
58             }
59         }
60 
61         if (!supportsMultiInstanceProperty) {
62             // If not checking the multi-instance properties, then return early
63             return false;
64         }
65 
66         // Check the activity property first
67         try {
68             val activityProp = packageManager.getProperty(
69                 PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI, componentName)
70             // If the above call doesn't throw a NameNotFoundException, then the activity property
71             // should override the application property value
72             if (activityProp.isBoolean) {
73                 KtProtoLog.v(WM_SHELL, "activity=%s supports multi-instance", componentName)
74                 return activityProp.boolean
75             } else {
76                 KtProtoLog.w(WM_SHELL, "Warning: property=%s for activity=%s has non-bool type=%d",
77                     PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI, packageName, activityProp.type)
78             }
79         } catch (nnfe: PackageManager.NameNotFoundException) {
80             // Not specified in the activity, fall through
81         }
82 
83         // Check the application property otherwise
84         try {
85             val appProp = packageManager.getProperty(
86                 PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI, packageName)
87             if (appProp.isBoolean) {
88                 KtProtoLog.v(WM_SHELL, "application=%s supports multi-instance", packageName)
89                 return appProp.boolean
90             } else {
91                 KtProtoLog.w(WM_SHELL,
92                     "Warning: property=%s for application=%s has non-bool type=%d",
93                     PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI, packageName, appProp.type)
94             }
95         } catch (nnfe: PackageManager.NameNotFoundException) {
96             // Not specified in either application or activity
97         }
98         return false
99     }
100 
101     companion object {
102         /** Returns the component from a PendingIntent  */
103         @JvmStatic
getComponentnull104         fun getComponent(pendingIntent: PendingIntent?): ComponentName? {
105             return pendingIntent?.intent?.component
106         }
107 
108         /** Returns the component from a shortcut  */
109         @JvmStatic
getShortcutComponentnull110         fun getShortcutComponent(packageName: String, shortcutId: String,
111                 user: UserHandle, launcherApps: LauncherApps): ComponentName? {
112             val query = LauncherApps.ShortcutQuery()
113             query.setPackage(packageName)
114             query.setShortcutIds(Arrays.asList(shortcutId))
115             query.setQueryFlags(LauncherApps.ShortcutQuery.FLAG_MATCH_ALL_KINDS_WITH_ALL_PINNED)
116             val shortcuts = launcherApps.getShortcuts(query, user)
117             val info = if (shortcuts != null && shortcuts.size > 0) shortcuts[0] else null
118             return info?.activity
119         }
120 
121         /** Returns true if package names and user ids match.  */
122         @JvmStatic
samePackagenull123         fun samePackage(packageName1: String?, packageName2: String?,
124                 userId1: Int, userId2: Int): Boolean {
125             return (packageName1 != null && packageName1 == packageName2) && (userId1 == userId2)
126         }
127     }
128 }
129