1 /*
<lambda>null2  * Copyright (C) 2019 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.launcher3.util
17 
18 import android.util.Xml
19 import com.android.launcher3.AutoInstallsLayout.ATTR_CLASS_NAME
20 import com.android.launcher3.AutoInstallsLayout.ATTR_CONTAINER
21 import com.android.launcher3.AutoInstallsLayout.ATTR_PACKAGE_NAME
22 import com.android.launcher3.AutoInstallsLayout.ATTR_RANK
23 import com.android.launcher3.AutoInstallsLayout.ATTR_SCREEN
24 import com.android.launcher3.AutoInstallsLayout.ATTR_SHORTCUT_ID
25 import com.android.launcher3.AutoInstallsLayout.ATTR_SPAN_X
26 import com.android.launcher3.AutoInstallsLayout.ATTR_SPAN_Y
27 import com.android.launcher3.AutoInstallsLayout.ATTR_TITLE
28 import com.android.launcher3.AutoInstallsLayout.ATTR_TITLE_TEXT
29 import com.android.launcher3.AutoInstallsLayout.ATTR_USER_TYPE
30 import com.android.launcher3.AutoInstallsLayout.ATTR_X
31 import com.android.launcher3.AutoInstallsLayout.ATTR_Y
32 import com.android.launcher3.AutoInstallsLayout.TAG_APPWIDGET
33 import com.android.launcher3.AutoInstallsLayout.TAG_AUTO_INSTALL
34 import com.android.launcher3.AutoInstallsLayout.TAG_FOLDER
35 import com.android.launcher3.AutoInstallsLayout.TAG_SHORTCUT
36 import com.android.launcher3.AutoInstallsLayout.TAG_WORKSPACE
37 import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP
38 import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT
39 import com.android.launcher3.LauncherSettings.Favorites.containerToString
40 import java.io.IOException
41 import java.io.StringWriter
42 import java.io.Writer
43 import org.xmlpull.v1.XmlSerializer
44 
45 /** Helper class to build xml for Launcher Layout */
46 class LauncherLayoutBuilder {
47     private val nodes = ArrayList<Node>()
48 
49     fun atHotseat(rank: Int) =
50         ItemTarget(
51             mapOf(
52                 ATTR_CONTAINER to containerToString(CONTAINER_HOTSEAT),
53                 ATTR_RANK to rank.toString()
54             )
55         )
56 
57     fun atWorkspace(x: Int, y: Int, screen: Int) =
58         ItemTarget(
59             mapOf(
60                 ATTR_CONTAINER to containerToString(CONTAINER_DESKTOP),
61                 ATTR_X to x.toString(),
62                 ATTR_Y to y.toString(),
63                 ATTR_SCREEN to screen.toString()
64             )
65         )
66 
67     @Throws(IOException::class) fun build() = StringWriter().apply { build(this) }.toString()
68 
69     @Throws(IOException::class)
70     fun build(writer: Writer) {
71         Xml.newSerializer().apply {
72             setOutput(writer)
73             setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true)
74             startDocument("UTF-8", true)
75             startTag(null, TAG_WORKSPACE)
76             writeNodes(nodes)
77             endTag(null, TAG_WORKSPACE)
78             endDocument()
79             flush()
80         }
81     }
82 
83     open inner class ItemTarget(private val baseValues: Map<String, String>) {
84         @JvmOverloads
85         fun putApp(packageName: String, className: String?, userType: String? = null) =
86             addItem(
87                 TAG_AUTO_INSTALL,
88                 userType,
89                 mapOf(
90                     ATTR_PACKAGE_NAME to packageName,
91                     ATTR_CLASS_NAME to (className ?: packageName)
92                 )
93             )
94 
95         @JvmOverloads
96         fun putShortcut(packageName: String, shortcutId: String, userType: String? = null) =
97             addItem(
98                 TAG_SHORTCUT,
99                 userType,
100                 mapOf(ATTR_PACKAGE_NAME to packageName, ATTR_SHORTCUT_ID to shortcutId)
101             )
102 
103         @JvmOverloads
104         fun putWidget(
105             packageName: String,
106             className: String,
107             spanX: Int,
108             spanY: Int,
109             userType: String? = null
110         ) =
111             addItem(
112                 TAG_APPWIDGET,
113                 userType,
114                 mapOf(
115                     ATTR_PACKAGE_NAME to packageName,
116                     ATTR_CLASS_NAME to className,
117                     ATTR_SPAN_X to spanX.toString(),
118                     ATTR_SPAN_Y to spanY.toString()
119                 )
120             )
121 
122         fun putFolder(titleResId: Int) = putFolder(ATTR_TITLE, titleResId.toString())
123 
124         fun putFolder(title: String?) = putFolder(ATTR_TITLE_TEXT, title)
125 
126         protected open fun addItem(
127             tag: String,
128             userType: String?,
129             props: Map<String, String>,
130             children: List<Node>? = null
131         ): LauncherLayoutBuilder {
132             nodes.add(
133                 Node(
134                     tag,
135                     HashMap(baseValues).apply {
136                         putAll(props)
137                         userType?.let { put(ATTR_USER_TYPE, it) }
138                     },
139                     children
140                 )
141             )
142             return this@LauncherLayoutBuilder
143         }
144 
145         protected open fun putFolder(titleKey: String, titleValue: String?): FolderBuilder {
146             val folderBuilder = FolderBuilder()
147             addItem(TAG_FOLDER, null, mapOf(titleKey to (titleValue ?: "")), folderBuilder.children)
148             return folderBuilder
149         }
150     }
151 
152     inner class FolderBuilder : ItemTarget(mapOf()) {
153 
154         val children = ArrayList<Node>()
155 
156         fun addApp(packageName: String, className: String?): FolderBuilder {
157             putApp(packageName, className)
158             return this
159         }
160 
161         fun addShortcut(packageName: String, shortcutId: String): FolderBuilder {
162             putShortcut(packageName, shortcutId)
163             return this
164         }
165 
166         override fun addItem(
167             tag: String,
168             userType: String?,
169             props: Map<String, String>,
170             childrenIgnored: List<Node>?
171         ): LauncherLayoutBuilder {
172             children.add(
173                 Node(tag, HashMap(props).apply { userType?.let { put(ATTR_USER_TYPE, it) } })
174             )
175             return this@LauncherLayoutBuilder
176         }
177 
178         override fun putFolder(titleKey: String, titleValue: String?): FolderBuilder {
179             throw IllegalArgumentException("Can't have folder inside a folder")
180         }
181 
182         fun build() = this@LauncherLayoutBuilder
183     }
184 
185     @Throws(IOException::class)
186     private fun XmlSerializer.writeNodes(nodes: List<Node>) {
187         nodes.forEach { node ->
188             startTag(null, node.name)
189             node.attrs.forEach { (key, value) -> attribute(null, key, value) }
190             node.children?.let { writeNodes(it) }
191             endTag(null, node.name)
192         }
193     }
194 
195     data class Node(
196         val name: String,
197         val attrs: Map<String, String>,
198         val children: List<Node>? = null
199     )
200 }
201