1 /*
2  * Copyright (C) 2023 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 
17 package android.tools.traces.component
18 
19 /**
20  * Create a new component identifier.
21  *
22  * This is a version of Android's ComponentName class for flicker. This is necessary because flicker
23  * codebase it also compiled into KotlinJS for use into Winscope
24  *
25  * @param packageName The name of the package that the component exists in. Can not be null.
26  * @param className The name of the class inside <var>pkg</var> that implements the component.
27  */
28 data class ComponentName(override val packageName: String, override val className: String) :
29     IComponentName {
30     /**
31      * Obtains the activity name from the component name.
32      *
33      * See [ComponentName.toWindowName] for additional information
34      */
toActivityNamenull35     override fun toActivityName(): String {
36         return when {
37             packageName.isNotEmpty() && className.isNotEmpty() -> {
38                 val sb = StringBuilder(packageName.length + className.length)
39                 appendShortString(sb, packageName, className)
40                 return sb.toString()
41             }
42             packageName.isNotEmpty() -> packageName
43             className.isNotEmpty() -> className
44             else -> error("Component name should have an activity of class name")
45         }
46     }
47 
48     /**
49      * Obtains the window name from the component name.
50      *
51      * [ComponentName] builds the string representation as PKG/CLASS, however this doesn't work for
52      * system components such as IME, NavBar and StatusBar, Toast.
53      *
54      * If the component doesn't have a package name, assume it's a system component and return only
55      * the class name
56      */
toWindowNamenull57     override fun toWindowName(): String {
58         return when {
59             packageName.isNotEmpty() && className.isNotEmpty() -> "$packageName/$className"
60             packageName.isNotEmpty() -> packageName
61             className.isNotEmpty() -> className
62             else -> error("Component name should have an activity of class name")
63         }
64     }
65 
toShortWindowNamenull66     internal fun toShortWindowName(): String {
67         return when {
68             packageName.isNotEmpty() && className.isNotEmpty() ->
69                 "$packageName/${className.removePrefix(packageName)}"
70             packageName.isNotEmpty() -> packageName
71             className.isNotEmpty() -> className
72             else -> error("Component name should have an activity of class name")
73         }
74     }
75 
76     /**
77      * Obtains the layer name from the component name.
78      *
79      * See [toWindowName] for additional information
80      */
toLayerNamenull81     override fun toLayerName(): String {
82         var result = this.toWindowName()
83         if (result.contains("/") && !result.contains("#")) {
84             result = "$result#"
85         }
86 
87         return result
88     }
89 
appendShortStringnull90     private fun appendShortString(sb: StringBuilder, packageName: String, className: String) {
91         sb.append(packageName).append('/')
92         appendShortClassName(sb, packageName, className)
93     }
94 
appendShortClassNamenull95     private fun appendShortClassName(sb: StringBuilder, packageName: String, className: String) {
96         if (className.startsWith(packageName)) {
97             val packageNameLength = packageName.length
98             val classNameLength = className.length
99             if (classNameLength > packageNameLength && className[packageNameLength] == '.') {
100                 sb.append(className, packageNameLength, classNameLength)
101                 return
102             }
103         }
104         sb.append(className)
105     }
106 
toStringnull107     override fun toString(): String = toShortWindowName()
108 
109     companion object {
110         fun fromLayerName(layerName: String): IComponentName {
111             var name = layerName
112             var packageName = ""
113             var className = ""
114             if (name.contains("/")) {
115                 if (name.contains("#")) {
116                     name = name.removeSuffix("#")
117                 }
118                 val splitString = name.split('/')
119                 packageName = splitString[0]
120                 className = splitString[1]
121             } else {
122                 className = name
123             }
124             return ComponentName(packageName, className)
125         }
126     }
127 }
128