• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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