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