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