/* * Copyright (C) 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.launcher3.util import android.util.Xml import com.android.launcher3.AutoInstallsLayout.ATTR_CLASS_NAME import com.android.launcher3.AutoInstallsLayout.ATTR_CONTAINER import com.android.launcher3.AutoInstallsLayout.ATTR_PACKAGE_NAME import com.android.launcher3.AutoInstallsLayout.ATTR_RANK import com.android.launcher3.AutoInstallsLayout.ATTR_SCREEN import com.android.launcher3.AutoInstallsLayout.ATTR_SHORTCUT_ID import com.android.launcher3.AutoInstallsLayout.ATTR_SPAN_X import com.android.launcher3.AutoInstallsLayout.ATTR_SPAN_Y import com.android.launcher3.AutoInstallsLayout.ATTR_TITLE import com.android.launcher3.AutoInstallsLayout.ATTR_TITLE_TEXT import com.android.launcher3.AutoInstallsLayout.ATTR_USER_TYPE import com.android.launcher3.AutoInstallsLayout.ATTR_X import com.android.launcher3.AutoInstallsLayout.ATTR_Y import com.android.launcher3.AutoInstallsLayout.TAG_APPWIDGET import com.android.launcher3.AutoInstallsLayout.TAG_AUTO_INSTALL import com.android.launcher3.AutoInstallsLayout.TAG_FOLDER import com.android.launcher3.AutoInstallsLayout.TAG_SHORTCUT import com.android.launcher3.AutoInstallsLayout.TAG_WORKSPACE import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT import com.android.launcher3.LauncherSettings.Favorites.containerToString import java.io.IOException import java.io.StringWriter import java.io.Writer import org.xmlpull.v1.XmlSerializer /** Helper class to build xml for Launcher Layout */ class LauncherLayoutBuilder { private val nodes = ArrayList() fun atHotseat(rank: Int) = ItemTarget( mapOf( ATTR_CONTAINER to containerToString(CONTAINER_HOTSEAT), ATTR_RANK to rank.toString() ) ) fun atWorkspace(x: Int, y: Int, screen: Int) = ItemTarget( mapOf( ATTR_CONTAINER to containerToString(CONTAINER_DESKTOP), ATTR_X to x.toString(), ATTR_Y to y.toString(), ATTR_SCREEN to screen.toString() ) ) @Throws(IOException::class) fun build() = StringWriter().apply { build(this) }.toString() @Throws(IOException::class) fun build(writer: Writer) { Xml.newSerializer().apply { setOutput(writer) setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true) startDocument("UTF-8", true) startTag(null, TAG_WORKSPACE) writeNodes(nodes) endTag(null, TAG_WORKSPACE) endDocument() flush() } } open inner class ItemTarget(private val baseValues: Map) { @JvmOverloads fun putApp(packageName: String, className: String?, userType: String? = null) = addItem( TAG_AUTO_INSTALL, userType, mapOf( ATTR_PACKAGE_NAME to packageName, ATTR_CLASS_NAME to (className ?: packageName) ) ) @JvmOverloads fun putShortcut(packageName: String, shortcutId: String, userType: String? = null) = addItem( TAG_SHORTCUT, userType, mapOf(ATTR_PACKAGE_NAME to packageName, ATTR_SHORTCUT_ID to shortcutId) ) @JvmOverloads fun putWidget( packageName: String, className: String, spanX: Int, spanY: Int, userType: String? = null ) = addItem( TAG_APPWIDGET, userType, mapOf( ATTR_PACKAGE_NAME to packageName, ATTR_CLASS_NAME to className, ATTR_SPAN_X to spanX.toString(), ATTR_SPAN_Y to spanY.toString() ) ) fun putFolder(titleResId: Int) = putFolder(ATTR_TITLE, titleResId.toString()) fun putFolder(title: String?) = putFolder(ATTR_TITLE_TEXT, title) protected open fun addItem( tag: String, userType: String?, props: Map, children: List? = null ): LauncherLayoutBuilder { nodes.add( Node( tag, HashMap(baseValues).apply { putAll(props) userType?.let { put(ATTR_USER_TYPE, it) } }, children ) ) return this@LauncherLayoutBuilder } protected open fun putFolder(titleKey: String, titleValue: String?): FolderBuilder { val folderBuilder = FolderBuilder() addItem(TAG_FOLDER, null, mapOf(titleKey to (titleValue ?: "")), folderBuilder.children) return folderBuilder } } inner class FolderBuilder : ItemTarget(mapOf()) { val children = ArrayList() fun addApp(packageName: String, className: String?): FolderBuilder { putApp(packageName, className) return this } fun addShortcut(packageName: String, shortcutId: String): FolderBuilder { putShortcut(packageName, shortcutId) return this } override fun addItem( tag: String, userType: String?, props: Map, childrenIgnored: List? ): LauncherLayoutBuilder { children.add( Node(tag, HashMap(props).apply { userType?.let { put(ATTR_USER_TYPE, it) } }) ) return this@LauncherLayoutBuilder } override fun putFolder(titleKey: String, titleValue: String?): FolderBuilder { throw IllegalArgumentException("Can't have folder inside a folder") } fun build() = this@LauncherLayoutBuilder } @Throws(IOException::class) private fun XmlSerializer.writeNodes(nodes: List) { nodes.forEach { node -> startTag(null, node.name) node.attrs.forEach { (key, value) -> attribute(null, key, value) } node.children?.let { writeNodes(it) } endTag(null, node.name) } } data class Node( val name: String, val attrs: Map, val children: List? = null ) }