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