1 /* 2 * 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 17 package com.android.launcher3.provider; 18 19 import static com.android.launcher3.icons.IconCache.EXTRA_SHORTCUT_BADGE_OVERRIDE_PACKAGE; 20 21 import android.content.ContentValues; 22 import android.content.Context; 23 import android.content.Intent; 24 import android.content.pm.ResolveInfo; 25 import android.content.pm.ShortcutInfo; 26 import android.database.Cursor; 27 import android.database.sqlite.SQLiteDatabase; 28 import android.graphics.Bitmap; 29 import android.graphics.BitmapFactory; 30 import android.graphics.drawable.Icon; 31 import android.os.PersistableBundle; 32 import android.os.Process; 33 import android.os.UserManager; 34 import android.text.TextUtils; 35 36 import com.android.launcher3.LauncherAppState; 37 import com.android.launcher3.LauncherSettings.Favorites; 38 import com.android.launcher3.Utilities; 39 import com.android.launcher3.model.LoaderCursor; 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 /** 48 * A set of utility methods for Launcher DB used for DB updates and migration. 49 */ 50 public class LauncherDbUtils { 51 52 /** 53 * Returns a string which can be used as a where clause for DB query to match the given itemId 54 */ itemIdMatch(int itemId)55 public static String itemIdMatch(int itemId) { 56 return "_id=" + itemId; 57 } 58 queryIntArray(boolean distinct, SQLiteDatabase db, String tableName, String columnName, String selection, String groupBy, String orderBy)59 public static IntArray queryIntArray(boolean distinct, SQLiteDatabase db, String tableName, 60 String columnName, String selection, String groupBy, String orderBy) { 61 IntArray out = new IntArray(); 62 try (Cursor c = db.query(distinct, tableName, new String[] { columnName }, selection, null, 63 groupBy, null, orderBy, null)) { 64 while (c.moveToNext()) { 65 out.add(c.getInt(0)); 66 } 67 } 68 return out; 69 } 70 tableExists(SQLiteDatabase db, String tableName)71 public static boolean tableExists(SQLiteDatabase db, String tableName) { 72 try (Cursor c = db.query(true, "sqlite_master", new String[] {"tbl_name"}, 73 "tbl_name = ?", new String[] {tableName}, 74 null, null, null, null, null)) { 75 return c.getCount() > 0; 76 } 77 } 78 dropTable(SQLiteDatabase db, String tableName)79 public static void dropTable(SQLiteDatabase db, String tableName) { 80 db.execSQL("DROP TABLE IF EXISTS " + tableName); 81 } 82 83 /** Copy fromTable in fromDb to toTable in toDb. */ copyTable(SQLiteDatabase fromDb, String fromTable, SQLiteDatabase toDb, String toTable, Context context)84 public static void copyTable(SQLiteDatabase fromDb, String fromTable, SQLiteDatabase toDb, 85 String toTable, Context context) { 86 long userSerial = UserCache.INSTANCE.get(context).getSerialNumberForUser( 87 Process.myUserHandle()); 88 dropTable(toDb, toTable); 89 Favorites.addTableToDb(toDb, userSerial, false, toTable); 90 if (fromDb != toDb) { 91 toDb.execSQL("ATTACH DATABASE '" + fromDb.getPath() + "' AS from_db"); 92 toDb.execSQL( 93 "INSERT INTO " + toTable + " SELECT * FROM from_db." + fromTable); 94 toDb.execSQL("DETACH DATABASE 'from_db'"); 95 } else { 96 toDb.execSQL("INSERT INTO " + toTable + " SELECT * FROM " + fromTable); 97 } 98 } 99 100 /** 101 * Migrates the legacy shortcuts to deep shortcuts pinned under Launcher. 102 * Removes any invalid shortcut or any shortcut which requires some permission to launch 103 */ migrateLegacyShortcuts(Context context, SQLiteDatabase db)104 public static void migrateLegacyShortcuts(Context context, SQLiteDatabase db) { 105 Cursor c = db.query( 106 Favorites.TABLE_NAME, null, "itemType = 1", null, null, null, null); 107 UserManagerState ums = new UserManagerState(); 108 ums.init(UserCache.INSTANCE.get(context), 109 context.getSystemService(UserManager.class)); 110 LoaderCursor lc = new LoaderCursor(c, LauncherAppState.getInstance(context), ums); 111 IntSet deletedShortcuts = new IntSet(); 112 113 while (lc.moveToNext()) { 114 if (lc.user != Process.myUserHandle()) { 115 deletedShortcuts.add(lc.id); 116 continue; 117 } 118 Intent intent = lc.parseIntent(); 119 if (intent == null) { 120 deletedShortcuts.add(lc.id); 121 continue; 122 } 123 if (TextUtils.isEmpty(lc.getTitle())) { 124 deletedShortcuts.add(lc.id); 125 continue; 126 } 127 128 // Make sure the target intent can be launched without any permissions. Otherwise remove 129 // the shortcut 130 ResolveInfo ri = context.getPackageManager().resolveActivity(intent, 0); 131 if (ri == null || !TextUtils.isEmpty(ri.activityInfo.permission)) { 132 deletedShortcuts.add(lc.id); 133 continue; 134 } 135 PersistableBundle extras = new PersistableBundle(); 136 extras.putString(EXTRA_SHORTCUT_BADGE_OVERRIDE_PACKAGE, ri.activityInfo.packageName); 137 ShortcutInfo.Builder infoBuilder = new ShortcutInfo.Builder( 138 context, "migrated_shortcut-" + lc.id) 139 .setIntent(intent) 140 .setExtras(extras) 141 .setShortLabel(lc.getTitle()); 142 143 Bitmap bitmap = null; 144 byte[] iconData = lc.getIconBlob(); 145 if (iconData != null) { 146 bitmap = BitmapFactory.decodeByteArray(iconData, 0, iconData.length); 147 } 148 if (bitmap != null) { 149 infoBuilder.setIcon(Icon.createWithBitmap(bitmap)); 150 } 151 152 ShortcutInfo info = infoBuilder.build(); 153 if (!PinRequestHelper.createRequestForShortcut(context, info).accept()) { 154 deletedShortcuts.add(lc.id); 155 continue; 156 } 157 ContentValues update = new ContentValues(); 158 update.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_DEEP_SHORTCUT); 159 update.put(Favorites.INTENT, 160 ShortcutKey.makeIntent(info.getId(), context.getPackageName()).toUri(0)); 161 db.update(Favorites.TABLE_NAME, update, "_id = ?", 162 new String[] {Integer.toString(lc.id)}); 163 } 164 lc.close(); 165 if (!deletedShortcuts.isEmpty()) { 166 db.delete(Favorites.TABLE_NAME, 167 Utilities.createDbSelectionQuery(Favorites._ID, deletedShortcuts.getArray()), 168 null); 169 } 170 171 // Drop the unused columns 172 db.execSQL("ALTER TABLE " + Favorites.TABLE_NAME + " DROP COLUMN iconPackage;"); 173 db.execSQL("ALTER TABLE " + Favorites.TABLE_NAME + " DROP COLUMN iconResource;"); 174 } 175 176 /** 177 * Utility class to simplify managing sqlite transactions 178 */ 179 public static class SQLiteTransaction implements AutoCloseable { 180 private final SQLiteDatabase mDb; 181 SQLiteTransaction(SQLiteDatabase db)182 public SQLiteTransaction(SQLiteDatabase db) { 183 mDb = db; 184 db.beginTransaction(); 185 } 186 commit()187 public void commit() { 188 mDb.setTransactionSuccessful(); 189 } 190 191 @Override close()192 public void close() { 193 mDb.endTransaction(); 194 } 195 getDb()196 public SQLiteDatabase getDb() { 197 return mDb; 198 } 199 } 200 } 201