• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2016 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.provider
17 
18 import android.content.ContentValues
19 import android.content.Context
20 import android.content.pm.ShortcutInfo
21 import android.database.sqlite.SQLiteDatabase
22 import android.graphics.Bitmap
23 import android.graphics.BitmapFactory
24 import android.graphics.drawable.Icon
25 import android.os.PersistableBundle
26 import android.os.Process
27 import android.os.UserManager
28 import android.text.TextUtils
29 import com.android.launcher3.LauncherSettings
30 import com.android.launcher3.LauncherSettings.Favorites
31 import com.android.launcher3.LauncherSettings.Favorites.CONTAINER
32 import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP
33 import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT
34 import com.android.launcher3.LauncherSettings.Favorites.SCREEN
35 import com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME
36 import com.android.launcher3.LauncherSettings.Favorites._ID
37 import com.android.launcher3.Utilities
38 import com.android.launcher3.dagger.LauncherComponentProvider.appComponent
39 import com.android.launcher3.icons.IconCache
40 import com.android.launcher3.model.UserManagerState
41 import com.android.launcher3.pm.PinRequestHelper
42 import com.android.launcher3.pm.UserCache
43 import com.android.launcher3.shortcuts.ShortcutKey
44 import com.android.launcher3.util.IntArray
45 import com.android.launcher3.util.IntSet
46 
47 /** A set of utility methods for Launcher DB used for DB updates and migration. */
48 object LauncherDbUtils {
49     /**
50      * Returns a string which can be used as a where clause for DB query to match the given itemId
51      */
52     @JvmStatic fun itemIdMatch(itemId: Int): String = "_id=$itemId"
53 
54     /**
55      * Returns a string which can be used as a where clause for DB query to match the given
56      * workspace screens or hotseat or a collection in workspace screens or hotseat
57      */
58     @JvmStatic
59     fun selectionForWorkspaceScreen(vararg screens: Int) =
60         "$SCREEN in (${screens.joinToString()}) or $CONTAINER = $CONTAINER_HOTSEAT or $CONTAINER in (select $_ID from $TABLE_NAME where $SCREEN in (${screens.joinToString()}) or $CONTAINER = $CONTAINER_HOTSEAT)"
61 
62     @JvmStatic
63     fun queryIntArray(
64         distinct: Boolean,
65         db: SQLiteDatabase,
66         tableName: String,
67         columnName: String,
68         selection: String?,
69         groupBy: String?,
70         orderBy: String?,
71     ): IntArray {
72         val out = IntArray()
73         db.query(
74                 distinct,
75                 tableName,
76                 arrayOf(columnName),
77                 selection,
78                 null,
79                 groupBy,
80                 null,
81                 orderBy,
82                 null,
83             )
84             .use { c ->
85                 while (c.moveToNext()) {
86                     out.add(c.getInt(0))
87                 }
88             }
89         return out
90     }
91 
92     @JvmStatic
93     fun tableExists(db: SQLiteDatabase, tableName: String): Boolean =
94         db.query(
95                 /* distinct = */ true,
96                 /* table = */ "sqlite_master",
97                 /* columns = */ arrayOf("tbl_name"),
98                 /* selection = */ "tbl_name = ?",
99                 /* selectionArgs = */ arrayOf(tableName),
100                 /* groupBy = */ null,
101                 /* having = */ null,
102                 /* orderBy = */ null,
103                 /* limit = */ null,
104                 /* cancellationSignal = */ null,
105             )
106             .use { c ->
107                 return c.count > 0
108             }
109 
110     @JvmStatic
111     fun dropTable(db: SQLiteDatabase, tableName: String) =
112         db.execSQL("DROP TABLE IF EXISTS $tableName")
113 
114     /** Copy fromTable in fromDb to toTable in toDb. */
115     @JvmStatic
116     fun copyTable(
117         fromDb: SQLiteDatabase,
118         fromTable: String,
119         toDb: SQLiteDatabase,
120         toTable: String,
121         context: Context,
122     ) {
123         val userSerial = UserCache.INSTANCE[context].getSerialNumberForUser(Process.myUserHandle())
124         dropTable(toDb, toTable)
125         LauncherSettings.Favorites.addTableToDb(toDb, userSerial, false, toTable)
126         if (fromDb != toDb) {
127             toDb.run {
128                 execSQL("ATTACH DATABASE '${fromDb.path}' AS from_db")
129                 execSQL(
130                     "INSERT INTO $toTable SELECT ${LauncherSettings.Favorites.getColumns(userSerial)} FROM from_db.$fromTable"
131                 )
132                 execSQL("DETACH DATABASE 'from_db'")
133             }
134         } else {
135             toDb.run {
136                 execSQL(
137                     "INSERT INTO $toTable SELECT ${
138                         LauncherSettings.Favorites.getColumns(
139                             userSerial
140                         )
141                     } FROM $fromTable"
142                 )
143             }
144         }
145     }
146 
147     @JvmStatic
148     fun shiftWorkspaceByXCells(db: SQLiteDatabase, x: Int, toTable: String) {
149         db.run {
150             execSQL("UPDATE $toTable SET cellY = cellY + $x WHERE container = $CONTAINER_DESKTOP")
151         }
152     }
153 
154     /**
155      * Migrates the legacy shortcuts to deep shortcuts pinned under Launcher. Removes any invalid
156      * shortcut or any shortcut which requires some permission to launch
157      */
158     @JvmStatic
159     fun migrateLegacyShortcuts(context: Context, db: SQLiteDatabase) {
160         val c =
161             db.query(
162                 LauncherSettings.Favorites.TABLE_NAME,
163                 null,
164                 "itemType = 1",
165                 null,
166                 null,
167                 null,
168                 null,
169             )
170         val ums = UserManagerState()
171         ums.run {
172             init(UserCache.INSTANCE[context], context.getSystemService(UserManager::class.java))
173         }
174         val lc = context.appComponent.loaderCursorFactory.createLoaderCursor(c, ums, null)
175         val deletedShortcuts = IntSet()
176 
177         while (lc.moveToNext()) {
178             if (lc.user !== Process.myUserHandle()) {
179                 deletedShortcuts.add(lc.id)
180                 continue
181             }
182             val intent = lc.parseIntent()
183             if (intent == null) {
184                 deletedShortcuts.add(lc.id)
185                 continue
186             }
187             if (TextUtils.isEmpty(lc.title)) {
188                 deletedShortcuts.add(lc.id)
189                 continue
190             }
191 
192             // Make sure the target intent can be launched without any permissions. Otherwise remove
193             // the shortcut
194             val ri = context.packageManager.resolveActivity(intent, 0)
195             if (ri == null || !TextUtils.isEmpty(ri.activityInfo.permission)) {
196                 deletedShortcuts.add(lc.id)
197                 continue
198             }
199             val extras =
200                 PersistableBundle().apply {
201                     putString(
202                         IconCache.EXTRA_SHORTCUT_BADGE_OVERRIDE_PACKAGE,
203                         ri.activityInfo.packageName,
204                     )
205                 }
206             val infoBuilder =
207                 ShortcutInfo.Builder(context, "migrated_shortcut-${lc.id}")
208                     .setIntent(intent)
209                     .setExtras(extras)
210                     .setShortLabel(lc.title)
211 
212             var bitmap: Bitmap? = null
213             val iconData = lc.iconBlob
214             if (iconData != null) {
215                 bitmap = BitmapFactory.decodeByteArray(iconData, 0, iconData.size)
216             }
217             if (bitmap != null) {
218                 infoBuilder.setIcon(Icon.createWithBitmap(bitmap))
219             }
220 
221             val info = infoBuilder.build()
222             try {
223                 if (!PinRequestHelper.createRequestForShortcut(context, info).accept()) {
224                     deletedShortcuts.add(lc.id)
225                     continue
226                 }
227             } catch (e: Exception) {
228                 deletedShortcuts.add(lc.id)
229                 continue
230             }
231             val update =
232                 ContentValues().apply {
233                     put(
234                         LauncherSettings.Favorites.ITEM_TYPE,
235                         LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT,
236                     )
237                     put(
238                         LauncherSettings.Favorites.INTENT,
239                         ShortcutKey.makeIntent(info.id, context.packageName).toUri(0),
240                     )
241                 }
242             db.update(
243                 LauncherSettings.Favorites.TABLE_NAME,
244                 update,
245                 "_id = ?",
246                 arrayOf(lc.id.toString()),
247             )
248         }
249         lc.close()
250         if (deletedShortcuts.isEmpty.not()) {
251             db.delete(
252                 /* table = */ LauncherSettings.Favorites.TABLE_NAME,
253                 /* whereClause = */ Utilities.createDbSelectionQuery(
254                     LauncherSettings.Favorites._ID,
255                     deletedShortcuts.array,
256                 ),
257                 /* whereArgs = */ null,
258             )
259         }
260 
261         // Drop the unused columns
262         db.run {
263             execSQL("ALTER TABLE ${LauncherSettings.Favorites.TABLE_NAME} DROP COLUMN iconPackage;")
264             execSQL(
265                 "ALTER TABLE ${LauncherSettings.Favorites.TABLE_NAME} DROP COLUMN iconResource;"
266             )
267         }
268     }
269 
270     /** Utility class to simplify managing sqlite transactions */
271     class SQLiteTransaction(val db: SQLiteDatabase) : AutoCloseable {
272         init {
273             db.beginTransaction()
274         }
275 
276         fun commit() = db.setTransactionSuccessful()
277 
278         override fun close() = db.endTransaction()
279     }
280 }
281