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.launcher3.model
17 
18 import android.content.ContentValues
19 import android.content.Intent
20 import android.util.Log
21 import com.android.launcher3.LauncherSettings
22 import com.android.launcher3.LauncherSettings.Favorites.CELLX
23 import com.android.launcher3.LauncherSettings.Favorites.CELLY
24 import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION
25 import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET
26 import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR
27 import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_FOLDER
28 import com.android.launcher3.LauncherSettings.Favorites.SCREEN
29 import com.android.launcher3.LauncherSettings.Favorites.SPANX
30 import com.android.launcher3.LauncherSettings.Favorites.SPANY
31 import com.android.launcher3.model.data.ItemInfo
32 import com.android.launcher3.util.ContentWriter
33 import java.net.URISyntaxException
34 import java.util.Objects
35 
36 class DbEntry : ItemInfo(), Comparable<DbEntry> {
37     @JvmField var mIntent: String? = null
38     @JvmField var mProvider: String? = null
39     @JvmField var mFolderItems: MutableMap<String, Set<Int>> = HashMap()
40 
41     /** Id of the specific widget. */
42     @JvmField var appWidgetId: Int = NO_ID
43 
44     /** Comparator according to the reading order */
45     override fun compareTo(other: DbEntry): Int {
46         if (screenId != other.screenId) {
47             return screenId.compareTo(other.screenId)
48         }
49         if (cellY != other.cellY) {
50             return cellY.compareTo(other.cellY)
51         }
52         return cellX.compareTo(other.cellX)
53     }
54 
55     override fun equals(other: Any?): Boolean {
56         if (this === other) return true
57         if (other !is DbEntry) return false
58         return getEntryMigrationId() == other.getEntryMigrationId()
59     }
60 
61     override fun hashCode(): Int = Objects.hash(getEntryMigrationId())
62 
63     /**
64      * Puts the updated DbEntry values into ContentValues which we then use to insert the entry to
65      * the DB.
66      */
67     fun updateContentValues(values: ContentValues) =
68         values.apply {
69             put(SCREEN, screenId)
70             put(CELLX, cellX)
71             put(CELLY, cellY)
72             put(SPANX, spanX)
73             put(SPANY, spanY)
74         }
75 
76     override fun writeToValues(writer: ContentWriter) {
77         super.writeToValues(writer)
78         writer.put(LauncherSettings.Favorites.APPWIDGET_ID, appWidgetId)
79     }
80 
81     override fun readFromValues(values: ContentValues) {
82         super.readFromValues(values)
83         appWidgetId = values.getAsInteger(LauncherSettings.Favorites.APPWIDGET_ID)
84     }
85 
86     /**
87      * This id is not used in the DB is only used while doing the migration and it identifies an
88      * entry on each workspace. For example two calculator icons would have the same migration id
89      * even thought they have different database ids.
90      */
91     private fun getEntryMigrationId(): String? {
92         when (itemType) {
93             ITEM_TYPE_FOLDER,
94             ITEM_TYPE_APP_PAIR -> return getFolderMigrationId()
95             ITEM_TYPE_APPWIDGET ->
96                 // mProvider is the app the widget belongs to and appWidgetId it's the unique
97                 // is of the widget, we need both because if you remove a widget and then add it
98                 // again, then it can change and the WidgetProvider would not know the widget.
99                 return mProvider + appWidgetId
100             ITEM_TYPE_APPLICATION -> {
101                 val intentStr = mIntent?.let { cleanIntentString(it) }
102                 try {
103                     val i = Intent.parseUri(intentStr, 0)
104                     return Objects.requireNonNull(i.component).toString()
105                 } catch (e: Exception) {
106                     return intentStr
107                 }
108             }
109 
110             else -> return mIntent?.let { cleanIntentString(it) }
111         }
112     }
113 
114     /**
115      * This method should return an id that should be the same for two folders containing the same
116      * elements.
117      */
118     private fun getFolderMigrationId(): String =
119         mFolderItems.keys
120             .map { intentString: String ->
121                 mFolderItems[intentString]?.size.toString() + cleanIntentString(intentString)
122             }
123             .sorted()
124             .joinToString(",")
125 
126     /**
127      * This is needed because sourceBounds can change and make the id of two equal items different.
128      */
129     private fun cleanIntentString(intentStr: String): String {
130         try {
131             return Intent.parseUri(intentStr, 0).apply { sourceBounds = null }.toURI()
132         } catch (e: URISyntaxException) {
133             Log.e(TAG, "Unable to parse Intent string", e)
134             return intentStr
135         }
136     }
137 
138     companion object {
139         private const val TAG = "DbEntry"
140     }
141 }
142