blob: c0e64875812bcafe51f2a1f07b1f818ab01e3849 [file] [log] [blame]
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001/*
2 * Copyright (C) 2008 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
Daniel Sandler325dc232013-06-05 22:57:57 -040017package com.android.launcher3;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080018
Bjorn Bringertcd8fec02010-01-14 13:26:43 +000019import android.app.SearchManager;
The Android Open Source Project7376fae2009-03-11 12:11:58 -070020import android.appwidget.AppWidgetHost;
Mike Cleronb87bd162009-10-30 16:36:56 -070021import android.appwidget.AppWidgetManager;
Bjorn Bringertcd8fec02010-01-14 13:26:43 +000022import android.appwidget.AppWidgetProviderInfo;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080023import android.content.ComponentName;
Adam Cohen228da5a2011-07-27 22:23:47 -070024import android.content.ContentProvider;
Yura085c8532014-02-11 15:15:29 +000025import android.content.ContentProviderOperation;
26import android.content.ContentProviderResult;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080027import android.content.ContentResolver;
Adam Cohen228da5a2011-07-27 22:23:47 -070028import android.content.ContentUris;
29import android.content.ContentValues;
30import android.content.Context;
31import android.content.Intent;
Yura085c8532014-02-11 15:15:29 +000032import android.content.OperationApplicationException;
Michael Jurkab85f8a42012-04-25 15:48:32 -070033import android.content.SharedPreferences;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080034import android.content.pm.ActivityInfo;
Jason Monk41314972014-03-03 16:11:30 -050035import android.content.pm.ApplicationInfo;
Adam Cohen228da5a2011-07-27 22:23:47 -070036import android.content.pm.PackageManager;
Jason Monk41314972014-03-03 16:11:30 -050037import android.content.pm.ResolveInfo;
Adam Cohen228da5a2011-07-27 22:23:47 -070038import android.content.res.Resources;
Adam Cohen228da5a2011-07-27 22:23:47 -070039import android.content.res.XmlResourceParser;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080040import android.database.Cursor;
41import android.database.SQLException;
Adam Cohen228da5a2011-07-27 22:23:47 -070042import android.database.sqlite.SQLiteDatabase;
43import android.database.sqlite.SQLiteOpenHelper;
44import android.database.sqlite.SQLiteQueryBuilder;
45import android.database.sqlite.SQLiteStatement;
Joe Onorato0589f0f2010-02-08 13:44:00 -080046import android.graphics.Bitmap;
47import android.graphics.BitmapFactory;
Adam Cohen228da5a2011-07-27 22:23:47 -070048import android.net.Uri;
Winson Chungb3302ae2012-05-01 10:19:14 -070049import android.os.Bundle;
Adam Cohen228da5a2011-07-27 22:23:47 -070050import android.provider.Settings;
51import android.text.TextUtils;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080052import android.util.Log;
Dan Sandlerab5fa3a2014-03-06 23:48:04 -050053import android.util.SparseArray;
Adam Cohen228da5a2011-07-27 22:23:47 -070054
Sunny Goyal0fe505b2014-08-06 09:55:36 -070055import com.android.launcher3.AutoInstallsLayout.LayoutParserCallback;
56import com.android.launcher3.LauncherSettings.Favorites;
Kenny Guyed131872014-04-30 03:02:21 +010057import com.android.launcher3.compat.UserHandleCompat;
58import com.android.launcher3.compat.UserManagerCompat;
Chris Wrene523e702013-10-09 10:36:55 -040059import com.android.launcher3.config.ProviderConfig;
Michael Jurka8b805b12012-04-18 14:23:14 -070060
61import org.xmlpull.v1.XmlPullParser;
62import org.xmlpull.v1.XmlPullParserException;
63
Dan Sandlerd5024042014-01-09 15:01:33 -050064import java.io.File;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080065import java.io.IOException;
Mike Cleronb87bd162009-10-30 16:36:56 -070066import java.net.URISyntaxException;
Adam Cohen228da5a2011-07-27 22:23:47 -070067import java.util.ArrayList;
Adam Cohen71483f42014-05-15 14:04:01 -070068import java.util.Collections;
Dan Sandlerd5024042014-01-09 15:01:33 -050069import java.util.HashSet;
Bjorn Bringertcd8fec02010-01-14 13:26:43 +000070import java.util.List;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080071
The Android Open Source Project31dd5032009-03-03 19:32:27 -080072public class LauncherProvider extends ContentProvider {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -080073 private static final String TAG = "Launcher.LauncherProvider";
74 private static final boolean LOGD = false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080075
76 private static final String DATABASE_NAME = "launcher.db";
Winson Chung3d503fb2011-07-13 17:25:49 -070077
Kenny Guyed131872014-04-30 03:02:21 +010078 private static final int DATABASE_VERSION = 20;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080079
Adam Cohene25af792013-06-06 23:08:25 -070080 static final String OLD_AUTHORITY = "com.android.launcher2.settings";
Chris Wrene523e702013-10-09 10:36:55 -040081 static final String AUTHORITY = ProviderConfig.AUTHORITY;
Winson Chung3d503fb2011-07-13 17:25:49 -070082
Dan Sandlerf0b8dac2013-11-19 12:21:25 -050083 // Should we attempt to load anything from the com.android.launcher2 provider?
Dan Sandlerd5024042014-01-09 15:01:33 -050084 static final boolean IMPORT_LAUNCHER2_DATABASE = false;
Dan Sandlerf0b8dac2013-11-19 12:21:25 -050085
The Android Open Source Project31dd5032009-03-03 19:32:27 -080086 static final String TABLE_FAVORITES = "favorites";
Adam Cohendcd297f2013-06-18 13:13:40 -070087 static final String TABLE_WORKSPACE_SCREENS = "workspaceScreens";
The Android Open Source Project31dd5032009-03-03 19:32:27 -080088 static final String PARAMETER_NOTIFY = "notify";
Winson Chungc763c4e2013-07-19 13:49:06 -070089 static final String UPGRADED_FROM_OLD_DATABASE =
90 "UPGRADED_FROM_OLD_DATABASE";
91 static final String EMPTY_DATABASE_CREATED =
92 "EMPTY_DATABASE_CREATED";
The Android Open Source Project31dd5032009-03-03 19:32:27 -080093
Winson Chungb3302ae2012-05-01 10:19:14 -070094 private static final String ACTION_APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE =
Adam Cohene25af792013-06-06 23:08:25 -070095 "com.android.launcher.action.APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE";
Winson Chungb3302ae2012-05-01 10:19:14 -070096
Adam Cohena043fa82014-07-23 14:49:38 -070097 private static final String URI_PARAM_IS_EXTERNAL_ADD = "isExternalAdd";
98
Anjali Koppal67e7cae2014-03-13 12:14:12 -070099 private LauncherProviderChangeListener mListener;
100
Jeffrey Sharkey2bbcae12009-03-31 14:37:57 -0700101 /**
Romain Guy73b979d2009-06-09 12:57:21 -0700102 * {@link Uri} triggered at any registered {@link android.database.ContentObserver} when
Jeffrey Sharkey2bbcae12009-03-31 14:37:57 -0700103 * {@link AppWidgetHost#deleteHost()} is called during database creation.
104 * Use this to recall {@link AppWidgetHost#startListening()} if needed.
105 */
106 static final Uri CONTENT_APPWIDGET_RESET_URI =
107 Uri.parse("content://" + AUTHORITY + "/appWidgetReset");
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700108
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700109 private DatabaseHelper mOpenHelper;
Winson Chungc763c4e2013-07-19 13:49:06 -0700110 private static boolean sJustLoadedFromOldDb;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800111
112 @Override
113 public boolean onCreate() {
Daniel Sandlere4f98912013-06-25 15:13:26 -0400114 final Context context = getContext();
115 mOpenHelper = new DatabaseHelper(context);
116 LauncherAppState.setLauncherProvider(this);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800117 return true;
118 }
119
Winson Chung0b560dd2014-01-21 13:00:26 -0800120 public boolean wasNewDbCreated() {
121 return mOpenHelper.wasNewDbCreated();
122 }
123
Anjali Koppal67e7cae2014-03-13 12:14:12 -0700124 public void setLauncherProviderChangeListener(LauncherProviderChangeListener listener) {
125 mListener = listener;
126 }
127
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800128 @Override
129 public String getType(Uri uri) {
130 SqlArguments args = new SqlArguments(uri, null, null);
131 if (TextUtils.isEmpty(args.where)) {
132 return "vnd.android.cursor.dir/" + args.table;
133 } else {
134 return "vnd.android.cursor.item/" + args.table;
135 }
136 }
137
138 @Override
139 public Cursor query(Uri uri, String[] projection, String selection,
140 String[] selectionArgs, String sortOrder) {
141
142 SqlArguments args = new SqlArguments(uri, selection, selectionArgs);
143 SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
144 qb.setTables(args.table);
145
Romain Guy73b979d2009-06-09 12:57:21 -0700146 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800147 Cursor result = qb.query(db, projection, args.where, args.args, null, null, sortOrder);
148 result.setNotificationUri(getContext().getContentResolver(), uri);
149
150 return result;
151 }
152
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700153 private static long dbInsertAndCheck(DatabaseHelper helper,
154 SQLiteDatabase db, String table, String nullColumnHack, ContentValues values) {
Dan Sandlerd5024042014-01-09 15:01:33 -0500155 if (values == null) {
156 throw new RuntimeException("Error: attempting to insert null values");
157 }
Adam Cohen71483f42014-05-15 14:04:01 -0700158 if (!values.containsKey(LauncherSettings.ChangeLogColumns._ID)) {
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700159 throw new RuntimeException("Error: attempting to add item without specifying an id");
160 }
Chris Wren5dee7af2013-12-20 17:22:11 -0500161 helper.checkId(table, values);
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700162 return db.insert(table, nullColumnHack, values);
163 }
164
Adam Cohen228da5a2011-07-27 22:23:47 -0700165 private static void deleteId(SQLiteDatabase db, long id) {
166 Uri uri = LauncherSettings.Favorites.getContentUri(id, false);
167 SqlArguments args = new SqlArguments(uri, null, null);
168 db.delete(args.table, args.where, args.args);
169 }
170
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800171 @Override
172 public Uri insert(Uri uri, ContentValues initialValues) {
173 SqlArguments args = new SqlArguments(uri);
174
Adam Cohena043fa82014-07-23 14:49:38 -0700175 // In very limited cases, we support system|signature permission apps to add to the db
176 String externalAdd = uri.getQueryParameter(URI_PARAM_IS_EXTERNAL_ADD);
177 if (externalAdd != null && "true".equals(externalAdd)) {
178 if (!mOpenHelper.initializeExternalAdd(initialValues)) {
179 return null;
180 }
181 }
182
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800183 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
Chris Wren1ada10d2013-09-13 18:01:38 -0400184 addModifiedTime(initialValues);
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700185 final long rowId = dbInsertAndCheck(mOpenHelper, db, args.table, null, initialValues);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800186 if (rowId <= 0) return null;
187
188 uri = ContentUris.withAppendedId(uri, rowId);
189 sendNotify(uri);
190
191 return uri;
192 }
193
Adam Cohena043fa82014-07-23 14:49:38 -0700194
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800195 @Override
196 public int bulkInsert(Uri uri, ContentValues[] values) {
197 SqlArguments args = new SqlArguments(uri);
198
199 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
200 db.beginTransaction();
201 try {
202 int numValues = values.length;
203 for (int i = 0; i < numValues; i++) {
Chris Wren1ada10d2013-09-13 18:01:38 -0400204 addModifiedTime(values[i]);
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700205 if (dbInsertAndCheck(mOpenHelper, db, args.table, null, values[i]) < 0) {
206 return 0;
207 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800208 }
209 db.setTransactionSuccessful();
210 } finally {
211 db.endTransaction();
212 }
213
214 sendNotify(uri);
215 return values.length;
216 }
217
218 @Override
Yura085c8532014-02-11 15:15:29 +0000219 public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)
220 throws OperationApplicationException {
221 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
222 db.beginTransaction();
223 try {
224 ContentProviderResult[] result = super.applyBatch(operations);
225 db.setTransactionSuccessful();
226 return result;
227 } finally {
228 db.endTransaction();
229 }
230 }
231
232 @Override
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800233 public int delete(Uri uri, String selection, String[] selectionArgs) {
234 SqlArguments args = new SqlArguments(uri, selection, selectionArgs);
235
236 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
237 int count = db.delete(args.table, args.where, args.args);
238 if (count > 0) sendNotify(uri);
239
240 return count;
241 }
242
243 @Override
244 public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
245 SqlArguments args = new SqlArguments(uri, selection, selectionArgs);
246
Chris Wren1ada10d2013-09-13 18:01:38 -0400247 addModifiedTime(values);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800248 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
249 int count = db.update(args.table, values, args.where, args.args);
250 if (count > 0) sendNotify(uri);
251
252 return count;
253 }
254
255 private void sendNotify(Uri uri) {
256 String notify = uri.getQueryParameter(PARAMETER_NOTIFY);
257 if (notify == null || "true".equals(notify)) {
258 getContext().getContentResolver().notifyChange(uri, null);
259 }
Chris Wren1ada10d2013-09-13 18:01:38 -0400260
261 // always notify the backup agent
Chris Wren92aa4232013-10-04 11:29:36 -0400262 LauncherBackupAgentHelper.dataChanged(getContext());
Anjali Koppal67e7cae2014-03-13 12:14:12 -0700263 if (mListener != null) {
264 mListener.onLauncherProviderChange();
265 }
Chris Wren1ada10d2013-09-13 18:01:38 -0400266 }
267
268 private void addModifiedTime(ContentValues values) {
269 values.put(LauncherSettings.ChangeLogColumns.MODIFIED, System.currentTimeMillis());
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800270 }
271
Adam Cohendcd297f2013-06-18 13:13:40 -0700272 public long generateNewItemId() {
273 return mOpenHelper.generateNewItemId();
274 }
275
Winson Chungc763c4e2013-07-19 13:49:06 -0700276 public void updateMaxItemId(long id) {
277 mOpenHelper.updateMaxItemId(id);
278 }
279
Adam Cohendcd297f2013-06-18 13:13:40 -0700280 public long generateNewScreenId() {
281 return mOpenHelper.generateNewScreenId();
282 }
283
284 // This is only required one time while loading the workspace during the
285 // upgrade path, and should never be called from anywhere else.
286 public void updateMaxScreenId(long maxScreenId) {
287 mOpenHelper.updateMaxScreenId(maxScreenId);
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700288 }
289
Brian Muramatsu5524b492012-10-02 16:55:54 -0700290 /**
Adam Cohene25af792013-06-06 23:08:25 -0700291 * @param Should we load the old db for upgrade? first run only.
292 */
Winson Chungc763c4e2013-07-19 13:49:06 -0700293 synchronized public boolean justLoadedOldDb() {
Daniel Sandlercc8befa2013-06-11 14:45:48 -0400294 String spKey = LauncherAppState.getSharedPreferencesKey();
Adam Cohene25af792013-06-06 23:08:25 -0700295 SharedPreferences sp = getContext().getSharedPreferences(spKey, Context.MODE_PRIVATE);
296
Winson Chungc763c4e2013-07-19 13:49:06 -0700297 boolean loadedOldDb = false || sJustLoadedFromOldDb;
Adam Cohendcd297f2013-06-18 13:13:40 -0700298
Winson Chungc763c4e2013-07-19 13:49:06 -0700299 sJustLoadedFromOldDb = false;
300 if (sp.getBoolean(UPGRADED_FROM_OLD_DATABASE, false)) {
Adam Cohene25af792013-06-06 23:08:25 -0700301
302 SharedPreferences.Editor editor = sp.edit();
Winson Chungc763c4e2013-07-19 13:49:06 -0700303 editor.remove(UPGRADED_FROM_OLD_DATABASE);
Adam Cohene25af792013-06-06 23:08:25 -0700304 editor.commit();
Winson Chungc763c4e2013-07-19 13:49:06 -0700305 loadedOldDb = true;
Adam Cohene25af792013-06-06 23:08:25 -0700306 }
Winson Chungc763c4e2013-07-19 13:49:06 -0700307 return loadedOldDb;
Adam Cohene25af792013-06-06 23:08:25 -0700308 }
309
310 /**
Sunny Goyal42de82f2014-09-26 22:09:29 -0700311 * Clears all the data for a fresh start.
312 */
313 synchronized public void createEmptyDB() {
314 mOpenHelper.createEmptyDB(mOpenHelper.getWritableDatabase());
315 }
316
317 /**
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700318 * Loads the default workspace based on the following priority scheme:
319 * 1) From a package provided by play store
320 * 2) From a partner configuration APK, already in the system image
321 * 3) The default configuration for the particular device
Brian Muramatsu5524b492012-10-02 16:55:54 -0700322 */
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700323 synchronized public void loadDefaultFavoritesIfNecessary() {
Daniel Sandlercc8befa2013-06-11 14:45:48 -0400324 String spKey = LauncherAppState.getSharedPreferencesKey();
Michael Jurkab85f8a42012-04-25 15:48:32 -0700325 SharedPreferences sp = getContext().getSharedPreferences(spKey, Context.MODE_PRIVATE);
Adam Cohene25af792013-06-06 23:08:25 -0700326
Winson Chungc763c4e2013-07-19 13:49:06 -0700327 if (sp.getBoolean(EMPTY_DATABASE_CREATED, false)) {
Chris Wren5dee7af2013-12-20 17:22:11 -0500328 Log.d(TAG, "loading default workspace");
Michael Jurka45355c42012-10-08 13:21:35 +0200329
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700330 WorkspaceLoader loader = AutoInstallsLayout.get(getContext(),
331 mOpenHelper.mAppWidgetHost, mOpenHelper);
332
333 if (loader == null) {
Adam Cohen9b8f51f2014-05-30 15:34:09 -0700334 final Partner partner = Partner.get(getContext().getPackageManager());
335 if (partner != null && partner.hasDefaultLayout()) {
336 final Resources partnerRes = partner.getResources();
Adam Cohen4ae96ce2014-08-29 15:05:48 -0700337 int workspaceResId = partnerRes.getIdentifier(Partner.RES_DEFAULT_LAYOUT,
Adam Cohen9b8f51f2014-05-30 15:34:09 -0700338 "xml", partner.getPackageName());
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700339 if (workspaceResId != 0) {
340 loader = new SimpleWorkspaceLoader(mOpenHelper, partnerRes, workspaceResId);
341 }
Adam Cohen9b8f51f2014-05-30 15:34:09 -0700342 }
343 }
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700344
345 if (loader == null) {
346 loader = new SimpleWorkspaceLoader(mOpenHelper, getContext().getResources(),
347 getDefaultWorkspaceResourceId());
Brian Muramatsu5524b492012-10-02 16:55:54 -0700348 }
349
Michael Jurkab85f8a42012-04-25 15:48:32 -0700350 // Populate favorites table with initial favorites
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700351 SharedPreferences.Editor editor = sp.edit().remove(EMPTY_DATABASE_CREATED);
352 mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(), loader);
Michael Jurkab85f8a42012-04-25 15:48:32 -0700353 editor.commit();
354 }
355 }
356
Dan Sandlerd5024042014-01-09 15:01:33 -0500357 public void migrateLauncher2Shortcuts() {
358 mOpenHelper.migrateLauncher2Shortcuts(mOpenHelper.getWritableDatabase(),
Jason Monk0bfcceb2014-03-21 15:42:06 -0400359 Uri.parse(getContext().getString(R.string.old_launcher_provider_uri)));
Dan Sandlerd5024042014-01-09 15:01:33 -0500360 }
361
Nilesh Agrawalda41ea62013-12-09 14:17:49 -0800362 private static int getDefaultWorkspaceResourceId() {
Winson Chungbe876472014-05-14 14:15:20 -0700363 LauncherAppState app = LauncherAppState.getInstance();
364 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
Nilesh Agrawal16f3ea82014-01-09 17:14:01 -0800365 if (LauncherAppState.isDisableAllApps()) {
Winson Chungbe876472014-05-14 14:15:20 -0700366 return grid.defaultNoAllAppsLayoutId;
Nilesh Agrawalda41ea62013-12-09 14:17:49 -0800367 } else {
Winson Chungbe876472014-05-14 14:15:20 -0700368 return grid.defaultLayoutId;
Nilesh Agrawalda41ea62013-12-09 14:17:49 -0800369 }
370 }
371
Winson Chungc763c4e2013-07-19 13:49:06 -0700372 private static interface ContentValuesCallback {
373 public void onRow(ContentValues values);
374 }
375
Adam Cohen6dbe0492013-12-02 17:00:14 -0800376 private static boolean shouldImportLauncher2Database(Context context) {
377 boolean isTablet = context.getResources().getBoolean(R.bool.is_tablet);
378
379 // We don't import the old databse for tablets, as the grid size has changed.
380 return !isTablet && IMPORT_LAUNCHER2_DATABASE;
381 }
382
Dan Sandlerd5024042014-01-09 15:01:33 -0500383 public void deleteDatabase() {
384 // Are you sure? (y/n)
385 final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
Dan Sandler2b471742014-01-21 14:14:41 -0500386 final File dbFile = new File(db.getPath());
Dan Sandlerd5024042014-01-09 15:01:33 -0500387 mOpenHelper.close();
Dan Sandler2b471742014-01-21 14:14:41 -0500388 if (dbFile.exists()) {
389 SQLiteDatabase.deleteDatabase(dbFile);
390 }
Dan Sandlerd5024042014-01-09 15:01:33 -0500391 mOpenHelper = new DatabaseHelper(getContext());
392 }
393
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700394 private static class DatabaseHelper extends SQLiteOpenHelper implements LayoutParserCallback {
Jason Monk41314972014-03-03 16:11:30 -0500395 private static final String TAG_RESOLVE = "resolve";
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800396 private static final String TAG_FAVORITES = "favorites";
397 private static final String TAG_FAVORITE = "favorite";
Mike Cleronb87bd162009-10-30 16:36:56 -0700398 private static final String TAG_APPWIDGET = "appwidget";
399 private static final String TAG_SHORTCUT = "shortcut";
Adam Cohen228da5a2011-07-27 22:23:47 -0700400 private static final String TAG_FOLDER = "folder";
Jeff Sharkey5aeef582014-04-14 13:26:42 -0700401 private static final String TAG_PARTNER_FOLDER = "partner-folder";
Winson Chungb3302ae2012-05-01 10:19:14 -0700402 private static final String TAG_EXTRA = "extra";
Daniel Sandler57dac262013-10-03 13:28:36 -0400403 private static final String TAG_INCLUDE = "include";
Winson Chung3d503fb2011-07-13 17:25:49 -0700404
Adam Cohen9b8f51f2014-05-30 15:34:09 -0700405 // Style attrs -- "Favorite"
Jeff Sharkey5aeef582014-04-14 13:26:42 -0700406 private static final String ATTR_CLASS_NAME = "className";
Adam Cohen9b8f51f2014-05-30 15:34:09 -0700407 private static final String ATTR_PACKAGE_NAME = "packageName";
408 private static final String ATTR_CONTAINER = "container";
409 private static final String ATTR_SCREEN = "screen";
410 private static final String ATTR_X = "x";
411 private static final String ATTR_Y = "y";
412 private static final String ATTR_SPAN_X = "spanX";
413 private static final String ATTR_SPAN_Y = "spanY";
414 private static final String ATTR_ICON = "icon";
415 private static final String ATTR_TITLE = "title";
416 private static final String ATTR_URI = "uri";
417
418 // Style attrs -- "Include"
419 private static final String ATTR_WORKSPACE = "workspace";
420
421 // Style attrs -- "Extra"
422 private static final String ATTR_KEY = "key";
423 private static final String ATTR_VALUE = "value";
Jeff Sharkey5aeef582014-04-14 13:26:42 -0700424
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800425 private final Context mContext;
Jeff Sharkey5aeef582014-04-14 13:26:42 -0700426 private final PackageManager mPackageManager;
The Android Open Source Project7376fae2009-03-11 12:11:58 -0700427 private final AppWidgetHost mAppWidgetHost;
Adam Cohendcd297f2013-06-18 13:13:40 -0700428 private long mMaxItemId = -1;
429 private long mMaxScreenId = -1;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800430
Winson Chung0b560dd2014-01-21 13:00:26 -0800431 private boolean mNewDbCreated = false;
432
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800433 DatabaseHelper(Context context) {
434 super(context, DATABASE_NAME, null, DATABASE_VERSION);
435 mContext = context;
Jeff Sharkey5aeef582014-04-14 13:26:42 -0700436 mPackageManager = context.getPackageManager();
The Android Open Source Project7376fae2009-03-11 12:11:58 -0700437 mAppWidgetHost = new AppWidgetHost(context, Launcher.APPWIDGET_HOST_ID);
Winson Chung3d503fb2011-07-13 17:25:49 -0700438
439 // In the case where neither onCreate nor onUpgrade gets called, we read the maxId from
440 // the DB here
Adam Cohendcd297f2013-06-18 13:13:40 -0700441 if (mMaxItemId == -1) {
442 mMaxItemId = initializeMaxItemId(getWritableDatabase());
443 }
444 if (mMaxScreenId == -1) {
445 mMaxScreenId = initializeMaxScreenId(getWritableDatabase());
Winson Chung3d503fb2011-07-13 17:25:49 -0700446 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800447 }
448
Winson Chung0b560dd2014-01-21 13:00:26 -0800449 public boolean wasNewDbCreated() {
450 return mNewDbCreated;
451 }
452
Jeffrey Sharkey2bbcae12009-03-31 14:37:57 -0700453 /**
454 * Send notification that we've deleted the {@link AppWidgetHost},
455 * probably as part of the initial database creation. The receiver may
456 * want to re-call {@link AppWidgetHost#startListening()} to ensure
457 * callbacks are correctly set.
458 */
459 private void sendAppWidgetResetNotify() {
460 final ContentResolver resolver = mContext.getContentResolver();
461 resolver.notifyChange(CONTENT_APPWIDGET_RESET_URI, null);
462 }
463
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800464 @Override
465 public void onCreate(SQLiteDatabase db) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800466 if (LOGD) Log.d(TAG, "creating new launcher database");
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700467
Adam Cohendcd297f2013-06-18 13:13:40 -0700468 mMaxItemId = 1;
469 mMaxScreenId = 0;
Winson Chung0b560dd2014-01-21 13:00:26 -0800470 mNewDbCreated = true;
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700471
Kenny Guyed131872014-04-30 03:02:21 +0100472 UserManagerCompat userManager = UserManagerCompat.getInstance(mContext);
473 long userSerialNumber = userManager.getSerialNumberForUser(
474 UserHandleCompat.myUserHandle());
475
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800476 db.execSQL("CREATE TABLE favorites (" +
477 "_id INTEGER PRIMARY KEY," +
478 "title TEXT," +
479 "intent TEXT," +
480 "container INTEGER," +
481 "screen INTEGER," +
482 "cellX INTEGER," +
483 "cellY INTEGER," +
484 "spanX INTEGER," +
485 "spanY INTEGER," +
486 "itemType INTEGER," +
The Android Open Source Projectca9475f2009-03-13 13:04:24 -0700487 "appWidgetId INTEGER NOT NULL DEFAULT -1," +
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800488 "isShortcut INTEGER," +
489 "iconType INTEGER," +
490 "iconPackage TEXT," +
491 "iconResource TEXT," +
492 "icon BLOB," +
493 "uri TEXT," +
Chris Wrend5e66bf2013-09-16 14:02:29 -0400494 "displayMode INTEGER," +
Chris Wren1ada10d2013-09-13 18:01:38 -0400495 "appWidgetProvider TEXT," +
Chris Wrenf4d08112014-01-16 18:13:56 -0500496 "modified INTEGER NOT NULL DEFAULT 0," +
Kenny Guyed131872014-04-30 03:02:21 +0100497 "restored INTEGER NOT NULL DEFAULT 0," +
498 "profileId INTEGER DEFAULT " + userSerialNumber +
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800499 ");");
Adam Cohendcd297f2013-06-18 13:13:40 -0700500 addWorkspacesTable(db);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800501
The Android Open Source Project7376fae2009-03-11 12:11:58 -0700502 // Database was just created, so wipe any previous widgets
503 if (mAppWidgetHost != null) {
504 mAppWidgetHost.deleteHost();
Jeffrey Sharkey2bbcae12009-03-31 14:37:57 -0700505 sendAppWidgetResetNotify();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800506 }
Winson Chung3d503fb2011-07-13 17:25:49 -0700507
Adam Cohen6dbe0492013-12-02 17:00:14 -0800508 if (shouldImportLauncher2Database(mContext)) {
Dan Sandlerf0b8dac2013-11-19 12:21:25 -0500509 // Try converting the old database
510 ContentValuesCallback permuteScreensCb = new ContentValuesCallback() {
511 public void onRow(ContentValues values) {
512 int container = values.getAsInteger(LauncherSettings.Favorites.CONTAINER);
513 if (container == Favorites.CONTAINER_DESKTOP) {
514 int screen = values.getAsInteger(LauncherSettings.Favorites.SCREEN);
515 screen = (int) upgradeLauncherDb_permuteScreens(screen);
516 values.put(LauncherSettings.Favorites.SCREEN, screen);
517 }
518 }
519 };
520 Uri uri = Uri.parse("content://" + Settings.AUTHORITY +
521 "/old_favorites?notify=true");
522 if (!convertDatabase(db, uri, permuteScreensCb, true)) {
523 // Try and upgrade from the Launcher2 db
Jason Monk0bfcceb2014-03-21 15:42:06 -0400524 uri = Uri.parse(mContext.getString(R.string.old_launcher_provider_uri));
Dan Sandlerf0b8dac2013-11-19 12:21:25 -0500525 if (!convertDatabase(db, uri, permuteScreensCb, false)) {
526 // If we fail, then set a flag to load the default workspace
527 setFlagEmptyDbCreated();
528 return;
Winson Chungc763c4e2013-07-19 13:49:06 -0700529 }
530 }
Dan Sandlerf0b8dac2013-11-19 12:21:25 -0500531 // Right now, in non-default workspace cases, we want to run the final
532 // upgrade code (ie. to fix workspace screen indices -> ids, etc.), so
533 // set that flag too.
534 setFlagJustLoadedOldDb();
535 } else {
536 // Fresh and clean launcher DB.
537 mMaxItemId = initializeMaxItemId(db);
538 setFlagEmptyDbCreated();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800539 }
540 }
541
Adam Cohendcd297f2013-06-18 13:13:40 -0700542 private void addWorkspacesTable(SQLiteDatabase db) {
543 db.execSQL("CREATE TABLE " + TABLE_WORKSPACE_SCREENS + " (" +
544 LauncherSettings.WorkspaceScreens._ID + " INTEGER," +
Chris Wren1ada10d2013-09-13 18:01:38 -0400545 LauncherSettings.WorkspaceScreens.SCREEN_RANK + " INTEGER," +
546 LauncherSettings.ChangeLogColumns.MODIFIED + " INTEGER NOT NULL DEFAULT 0" +
Adam Cohendcd297f2013-06-18 13:13:40 -0700547 ");");
548 }
549
Adam Cohen119285e2014-04-02 16:59:08 -0700550 private void removeOrphanedItems(SQLiteDatabase db) {
Adam Cohenf9c14de2014-04-17 18:20:45 -0700551 // Delete items directly on the workspace who's screen id doesn't exist
552 // "DELETE FROM favorites WHERE screen NOT IN (SELECT _id FROM workspaceScreens)
553 // AND container = -100"
554 String removeOrphanedDesktopItems = "DELETE FROM " + TABLE_FAVORITES +
555 " WHERE " +
Adam Cohen119285e2014-04-02 16:59:08 -0700556 LauncherSettings.Favorites.SCREEN + " NOT IN (SELECT " +
Adam Cohenf9c14de2014-04-17 18:20:45 -0700557 LauncherSettings.WorkspaceScreens._ID + " FROM " + TABLE_WORKSPACE_SCREENS + ")" +
558 " AND " +
559 LauncherSettings.Favorites.CONTAINER + " = " +
560 LauncherSettings.Favorites.CONTAINER_DESKTOP;
561 db.execSQL(removeOrphanedDesktopItems);
562
563 // Delete items contained in folders which no longer exist (after above statement)
564 // "DELETE FROM favorites WHERE container <> -100 AND container <> -101 AND container
565 // NOT IN (SELECT _id FROM favorites WHERE itemType = 2)"
566 String removeOrphanedFolderItems = "DELETE FROM " + TABLE_FAVORITES +
567 " WHERE " +
568 LauncherSettings.Favorites.CONTAINER + " <> " +
569 LauncherSettings.Favorites.CONTAINER_DESKTOP +
570 " AND "
571 + LauncherSettings.Favorites.CONTAINER + " <> " +
572 LauncherSettings.Favorites.CONTAINER_HOTSEAT +
573 " AND "
574 + LauncherSettings.Favorites.CONTAINER + " NOT IN (SELECT " +
575 LauncherSettings.Favorites._ID + " FROM " + TABLE_FAVORITES +
576 " WHERE " + LauncherSettings.Favorites.ITEM_TYPE + " = " +
577 LauncherSettings.Favorites.ITEM_TYPE_FOLDER + ")";
578 db.execSQL(removeOrphanedFolderItems);
Adam Cohen119285e2014-04-02 16:59:08 -0700579 }
580
Winson Chungc763c4e2013-07-19 13:49:06 -0700581 private void setFlagJustLoadedOldDb() {
Daniel Sandlercc8befa2013-06-11 14:45:48 -0400582 String spKey = LauncherAppState.getSharedPreferencesKey();
Michael Jurkab85f8a42012-04-25 15:48:32 -0700583 SharedPreferences sp = mContext.getSharedPreferences(spKey, Context.MODE_PRIVATE);
584 SharedPreferences.Editor editor = sp.edit();
Winson Chungc763c4e2013-07-19 13:49:06 -0700585 editor.putBoolean(UPGRADED_FROM_OLD_DATABASE, true);
586 editor.putBoolean(EMPTY_DATABASE_CREATED, false);
Michael Jurkab85f8a42012-04-25 15:48:32 -0700587 editor.commit();
588 }
589
Winson Chungc763c4e2013-07-19 13:49:06 -0700590 private void setFlagEmptyDbCreated() {
591 String spKey = LauncherAppState.getSharedPreferencesKey();
592 SharedPreferences sp = mContext.getSharedPreferences(spKey, Context.MODE_PRIVATE);
593 SharedPreferences.Editor editor = sp.edit();
594 editor.putBoolean(EMPTY_DATABASE_CREATED, true);
595 editor.putBoolean(UPGRADED_FROM_OLD_DATABASE, false);
596 editor.commit();
597 }
598
599 // We rearrange the screens from the old launcher
600 // 12345 -> 34512
601 private long upgradeLauncherDb_permuteScreens(long screen) {
602 if (screen >= 2) {
603 return screen - 2;
604 } else {
605 return screen + 3;
606 }
607 }
608
609 private boolean convertDatabase(SQLiteDatabase db, Uri uri,
610 ContentValuesCallback cb, boolean deleteRows) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800611 if (LOGD) Log.d(TAG, "converting database from an older format, but not onUpgrade");
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800612 boolean converted = false;
613
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800614 final ContentResolver resolver = mContext.getContentResolver();
615 Cursor cursor = null;
616
617 try {
618 cursor = resolver.query(uri, null, null, null, null);
619 } catch (Exception e) {
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700620 // Ignore
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800621 }
622
623 // We already have a favorites database in the old provider
Winson Chungc763c4e2013-07-19 13:49:06 -0700624 if (cursor != null) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800625 try {
Winson Chungc763c4e2013-07-19 13:49:06 -0700626 if (cursor.getCount() > 0) {
627 converted = copyFromCursor(db, cursor, cb) > 0;
628 if (converted && deleteRows) {
629 resolver.delete(uri, null, null);
630 }
631 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800632 } finally {
633 cursor.close();
634 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800635 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700636
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800637 if (converted) {
The Android Open Source Project7376fae2009-03-11 12:11:58 -0700638 // Convert widgets from this import into widgets
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800639 if (LOGD) Log.d(TAG, "converted and now triggering widget upgrade");
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800640 convertWidgets(db);
Winson Chungc763c4e2013-07-19 13:49:06 -0700641
642 // Update max item id
643 mMaxItemId = initializeMaxItemId(db);
644 if (LOGD) Log.d(TAG, "mMaxItemId: " + mMaxItemId);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800645 }
646
647 return converted;
648 }
649
Winson Chungc763c4e2013-07-19 13:49:06 -0700650 private int copyFromCursor(SQLiteDatabase db, Cursor c, ContentValuesCallback cb) {
Romain Guy73b979d2009-06-09 12:57:21 -0700651 final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800652 final int intentIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.INTENT);
653 final int titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE);
654 final int iconTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_TYPE);
655 final int iconIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON);
656 final int iconPackageIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_PACKAGE);
657 final int iconResourceIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_RESOURCE);
658 final int containerIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER);
659 final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE);
660 final int screenIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN);
661 final int cellXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX);
662 final int cellYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY);
663 final int uriIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.URI);
664 final int displayModeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.DISPLAY_MODE);
665
666 ContentValues[] rows = new ContentValues[c.getCount()];
667 int i = 0;
668 while (c.moveToNext()) {
669 ContentValues values = new ContentValues(c.getColumnCount());
Romain Guy73b979d2009-06-09 12:57:21 -0700670 values.put(LauncherSettings.Favorites._ID, c.getLong(idIndex));
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800671 values.put(LauncherSettings.Favorites.INTENT, c.getString(intentIndex));
672 values.put(LauncherSettings.Favorites.TITLE, c.getString(titleIndex));
673 values.put(LauncherSettings.Favorites.ICON_TYPE, c.getInt(iconTypeIndex));
674 values.put(LauncherSettings.Favorites.ICON, c.getBlob(iconIndex));
675 values.put(LauncherSettings.Favorites.ICON_PACKAGE, c.getString(iconPackageIndex));
676 values.put(LauncherSettings.Favorites.ICON_RESOURCE, c.getString(iconResourceIndex));
677 values.put(LauncherSettings.Favorites.CONTAINER, c.getInt(containerIndex));
678 values.put(LauncherSettings.Favorites.ITEM_TYPE, c.getInt(itemTypeIndex));
The Android Open Source Project7376fae2009-03-11 12:11:58 -0700679 values.put(LauncherSettings.Favorites.APPWIDGET_ID, -1);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800680 values.put(LauncherSettings.Favorites.SCREEN, c.getInt(screenIndex));
681 values.put(LauncherSettings.Favorites.CELLX, c.getInt(cellXIndex));
682 values.put(LauncherSettings.Favorites.CELLY, c.getInt(cellYIndex));
683 values.put(LauncherSettings.Favorites.URI, c.getString(uriIndex));
684 values.put(LauncherSettings.Favorites.DISPLAY_MODE, c.getInt(displayModeIndex));
Winson Chungc763c4e2013-07-19 13:49:06 -0700685 if (cb != null) {
686 cb.onRow(values);
687 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800688 rows[i++] = values;
689 }
690
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800691 int total = 0;
Winson Chungc763c4e2013-07-19 13:49:06 -0700692 if (i > 0) {
693 db.beginTransaction();
694 try {
695 int numValues = rows.length;
696 for (i = 0; i < numValues; i++) {
697 if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, rows[i]) < 0) {
698 return 0;
699 } else {
700 total++;
701 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800702 }
Winson Chungc763c4e2013-07-19 13:49:06 -0700703 db.setTransactionSuccessful();
704 } finally {
705 db.endTransaction();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800706 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800707 }
708
709 return total;
710 }
711
712 @Override
713 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Winson Chungc763c4e2013-07-19 13:49:06 -0700714 if (LOGD) Log.d(TAG, "onUpgrade triggered: " + oldVersion);
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700715
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800716 int version = oldVersion;
The Android Open Source Projectca9475f2009-03-13 13:04:24 -0700717 if (version < 3) {
718 // upgrade 1,2 -> 3 added appWidgetId column
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800719 db.beginTransaction();
720 try {
The Android Open Source Project7376fae2009-03-11 12:11:58 -0700721 // Insert new column for holding appWidgetIds
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800722 db.execSQL("ALTER TABLE favorites " +
The Android Open Source Projectca9475f2009-03-13 13:04:24 -0700723 "ADD COLUMN appWidgetId INTEGER NOT NULL DEFAULT -1;");
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800724 db.setTransactionSuccessful();
The Android Open Source Projectca9475f2009-03-13 13:04:24 -0700725 version = 3;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800726 } catch (SQLException ex) {
727 // Old version remains, which means we wipe old data
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800728 Log.e(TAG, ex.getMessage(), ex);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800729 } finally {
730 db.endTransaction();
731 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700732
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800733 // Convert existing widgets only if table upgrade was successful
The Android Open Source Projectca9475f2009-03-13 13:04:24 -0700734 if (version == 3) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800735 convertWidgets(db);
736 }
737 }
Romain Guy73b979d2009-06-09 12:57:21 -0700738
739 if (version < 4) {
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800740 version = 4;
Romain Guy73b979d2009-06-09 12:57:21 -0700741 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700742
Romain Guy509cd6a2010-03-23 15:10:56 -0700743 // Where's version 5?
744 // - Donut and sholes on 2.0 shipped with version 4 of launcher1.
Daniel Sandler325dc232013-06-05 22:57:57 -0400745 // - Passion shipped on 2.1 with version 6 of launcher3
Romain Guy509cd6a2010-03-23 15:10:56 -0700746 // - Sholes shipped on 2.1r1 (aka Mr. 3) with version 5 of launcher 1
747 // but version 5 on there was the updateContactsShortcuts change
748 // which was version 6 in launcher 2 (first shipped on passion 2.1r1).
749 // The updateContactsShortcuts change is idempotent, so running it twice
750 // is okay so we'll do that when upgrading the devices that shipped with it.
751 if (version < 6) {
Mike Cleron3a2b3f22009-11-05 17:17:42 -0800752 // We went from 3 to 5 screens. Move everything 1 to the right
753 db.beginTransaction();
754 try {
755 db.execSQL("UPDATE favorites SET screen=(screen + 1);");
756 db.setTransactionSuccessful();
Mike Cleron3a2b3f22009-11-05 17:17:42 -0800757 } catch (SQLException ex) {
758 // Old version remains, which means we wipe old data
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800759 Log.e(TAG, ex.getMessage(), ex);
Mike Cleron3a2b3f22009-11-05 17:17:42 -0800760 } finally {
761 db.endTransaction();
762 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700763
Romain Guy509cd6a2010-03-23 15:10:56 -0700764 // We added the fast track.
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800765 if (updateContactsShortcuts(db)) {
766 version = 6;
767 }
768 }
Bjorn Bringert7984c942009-12-09 15:38:25 +0000769
770 if (version < 7) {
771 // Version 7 gets rid of the special search widget.
772 convertWidgets(db);
773 version = 7;
774 }
775
Joe Onorato0589f0f2010-02-08 13:44:00 -0800776 if (version < 8) {
777 // Version 8 (froyo) has the icons all normalized. This should
778 // already be the case in practice, but we now rely on it and don't
779 // resample the images each time.
780 normalizeIcons(db);
781 version = 8;
782 }
783
Winson Chung3d503fb2011-07-13 17:25:49 -0700784 if (version < 9) {
785 // The max id is not yet set at this point (onUpgrade is triggered in the ctor
786 // before it gets a change to get set, so we need to read it here when we use it)
Adam Cohendcd297f2013-06-18 13:13:40 -0700787 if (mMaxItemId == -1) {
788 mMaxItemId = initializeMaxItemId(db);
Winson Chung3d503fb2011-07-13 17:25:49 -0700789 }
790
791 // Add default hotseat icons
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700792 loadFavorites(db, new SimpleWorkspaceLoader(this, mContext.getResources(),
793 R.xml.update_workspace));
Winson Chung3d503fb2011-07-13 17:25:49 -0700794 version = 9;
795 }
796
Daniel Lehmannd02402c2012-05-14 18:30:53 -0700797 // We bumped the version three time during JB, once to update the launch flags, once to
798 // update the override for the default launch animation and once to set the mimetype
799 // to improve startup performance
800 if (version < 12) {
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700801 // Contact shortcuts need a different set of flags to be launched now
802 // The updateContactsShortcuts change is idempotent, so we can keep using it like
803 // back in the Donut days
804 updateContactsShortcuts(db);
Daniel Lehmannd02402c2012-05-14 18:30:53 -0700805 version = 12;
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700806 }
807
Adam Cohendcd297f2013-06-18 13:13:40 -0700808 if (version < 13) {
809 // With the new shrink-wrapped and re-orderable workspaces, it makes sense
810 // to persist workspace screens and their relative order.
811 mMaxScreenId = 0;
812
813 // This will never happen in the wild, but when we switch to using workspace
814 // screen ids, redo the import from old launcher.
Winson Chungc763c4e2013-07-19 13:49:06 -0700815 sJustLoadedFromOldDb = true;
Adam Cohendcd297f2013-06-18 13:13:40 -0700816
817 addWorkspacesTable(db);
818 version = 13;
819 }
820
Chris Wrend5e66bf2013-09-16 14:02:29 -0400821 if (version < 14) {
822 db.beginTransaction();
823 try {
824 // Insert new column for holding widget provider name
825 db.execSQL("ALTER TABLE favorites " +
826 "ADD COLUMN appWidgetProvider TEXT;");
827 db.setTransactionSuccessful();
828 version = 14;
829 } catch (SQLException ex) {
830 // Old version remains, which means we wipe old data
831 Log.e(TAG, ex.getMessage(), ex);
832 } finally {
833 db.endTransaction();
834 }
835 }
836
Chris Wren1ada10d2013-09-13 18:01:38 -0400837 if (version < 15) {
838 db.beginTransaction();
839 try {
840 // Insert new column for holding update timestamp
841 db.execSQL("ALTER TABLE favorites " +
842 "ADD COLUMN modified INTEGER NOT NULL DEFAULT 0;");
843 db.execSQL("ALTER TABLE workspaceScreens " +
844 "ADD COLUMN modified INTEGER NOT NULL DEFAULT 0;");
845 db.setTransactionSuccessful();
846 version = 15;
847 } catch (SQLException ex) {
848 // Old version remains, which means we wipe old data
849 Log.e(TAG, ex.getMessage(), ex);
850 } finally {
851 db.endTransaction();
852 }
853 }
854
Chris Wrenf4d08112014-01-16 18:13:56 -0500855
856 if (version < 16) {
857 db.beginTransaction();
858 try {
859 // Insert new column for holding restore status
860 db.execSQL("ALTER TABLE favorites " +
861 "ADD COLUMN restored INTEGER NOT NULL DEFAULT 0;");
862 db.setTransactionSuccessful();
863 version = 16;
864 } catch (SQLException ex) {
865 // Old version remains, which means we wipe old data
866 Log.e(TAG, ex.getMessage(), ex);
867 } finally {
868 db.endTransaction();
869 }
870 }
871
Adam Cohen71e03b92014-02-21 14:09:53 -0800872 if (version < 17) {
873 // We use the db version upgrade here to identify users who may not have seen
874 // clings yet (because they weren't available), but for whom the clings are now
875 // available (tablet users). Because one of the possible cling flows (migration)
876 // is very destructive (wipes out workspaces), we want to prevent this from showing
877 // until clear data. We do so by marking that the clings have been shown.
878 LauncherClings.synchonouslyMarkFirstRunClingDismissed(mContext);
879 version = 17;
880 }
881
Adam Cohen119285e2014-04-02 16:59:08 -0700882 if (version < 18) {
Adam Cohenf9c14de2014-04-17 18:20:45 -0700883 // No-op
884 version = 18;
885 }
886
887 if (version < 19) {
Adam Cohen119285e2014-04-02 16:59:08 -0700888 // Due to a data loss bug, some users may have items associated with screen ids
889 // which no longer exist. Since this can cause other problems, and since the user
890 // will never see these items anyway, we use database upgrade as an opportunity to
891 // clean things up.
Adam Cohenf9c14de2014-04-17 18:20:45 -0700892 removeOrphanedItems(db);
893 version = 19;
Adam Cohen119285e2014-04-02 16:59:08 -0700894 }
895
Kenny Guyed131872014-04-30 03:02:21 +0100896 if (version < 20) {
897 // Add userId column
898 if (addProfileColumn(db)) {
899 version = 20;
900 }
901 // else old version remains, which means we wipe old data
902 }
903
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800904 if (version != DATABASE_VERSION) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800905 Log.w(TAG, "Destroying all old data.");
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800906 db.execSQL("DROP TABLE IF EXISTS " + TABLE_FAVORITES);
Adam Cohendcd297f2013-06-18 13:13:40 -0700907 db.execSQL("DROP TABLE IF EXISTS " + TABLE_WORKSPACE_SCREENS);
908
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800909 onCreate(db);
910 }
911 }
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800912
Adam Cohen9b1d0622014-05-21 19:01:57 -0700913 @Override
914 public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
915 // This shouldn't happen -- throw our hands up in the air and start over.
916 Log.w(TAG, "Database version downgrade from: " + oldVersion + " to " + newVersion +
917 ". Wiping databse.");
Sunny Goyal42de82f2014-09-26 22:09:29 -0700918 createEmptyDB(db);
919 }
Adam Cohen9b1d0622014-05-21 19:01:57 -0700920
Sunny Goyal42de82f2014-09-26 22:09:29 -0700921
922 /**
923 * Clears all the data for a fresh start.
924 */
925 public void createEmptyDB(SQLiteDatabase db) {
Adam Cohen9b1d0622014-05-21 19:01:57 -0700926 db.execSQL("DROP TABLE IF EXISTS " + TABLE_FAVORITES);
927 db.execSQL("DROP TABLE IF EXISTS " + TABLE_WORKSPACE_SCREENS);
928 onCreate(db);
929 }
930
Kenny Guyed131872014-04-30 03:02:21 +0100931 private boolean addProfileColumn(SQLiteDatabase db) {
932 db.beginTransaction();
933 try {
934 UserManagerCompat userManager = UserManagerCompat.getInstance(mContext);
935 // Default to the serial number of this user, for older
936 // shortcuts.
937 long userSerialNumber = userManager.getSerialNumberForUser(
938 UserHandleCompat.myUserHandle());
939 // Insert new column for holding user serial number
940 db.execSQL("ALTER TABLE favorites " +
941 "ADD COLUMN profileId INTEGER DEFAULT "
942 + userSerialNumber + ";");
943 db.setTransactionSuccessful();
944 } catch (SQLException ex) {
945 // Old version remains, which means we wipe old data
946 Log.e(TAG, ex.getMessage(), ex);
947 return false;
948 } finally {
949 db.endTransaction();
950 }
951 return true;
952 }
953
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800954 private boolean updateContactsShortcuts(SQLiteDatabase db) {
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800955 final String selectWhere = buildOrWhereString(Favorites.ITEM_TYPE,
956 new int[] { Favorites.ITEM_TYPE_SHORTCUT });
957
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700958 Cursor c = null;
959 final String actionQuickContact = "com.android.contacts.action.QUICK_CONTACT";
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800960 db.beginTransaction();
961 try {
962 // Select and iterate through each matching widget
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700963 c = db.query(TABLE_FAVORITES,
964 new String[] { Favorites._ID, Favorites.INTENT },
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800965 selectWhere, null, null, null, null);
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700966 if (c == null) return false;
967
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800968 if (LOGD) Log.d(TAG, "found upgrade cursor count=" + c.getCount());
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700969
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800970 final int idIndex = c.getColumnIndex(Favorites._ID);
971 final int intentIndex = c.getColumnIndex(Favorites.INTENT);
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700972
973 while (c.moveToNext()) {
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800974 long favoriteId = c.getLong(idIndex);
975 final String intentUri = c.getString(intentIndex);
976 if (intentUri != null) {
977 try {
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700978 final Intent intent = Intent.parseUri(intentUri, 0);
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800979 android.util.Log.d("Home", intent.toString());
980 final Uri uri = intent.getData();
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700981 if (uri != null) {
982 final String data = uri.toString();
983 if ((Intent.ACTION_VIEW.equals(intent.getAction()) ||
984 actionQuickContact.equals(intent.getAction())) &&
985 (data.startsWith("content://contacts/people/") ||
986 data.startsWith("content://com.android.contacts/" +
987 "contacts/lookup/"))) {
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800988
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700989 final Intent newIntent = new Intent(actionQuickContact);
990 // When starting from the launcher, start in a new, cleared task
991 // CLEAR_WHEN_TASK_RESET cannot reset the root of a task, so we
992 // clear the whole thing preemptively here since
993 // QuickContactActivity will finish itself when launching other
994 // detail activities.
995 newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
996 Intent.FLAG_ACTIVITY_CLEAR_TASK);
Winson Chung2672ff92012-05-04 16:22:30 -0700997 newIntent.putExtra(
998 Launcher.INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION, true);
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700999 newIntent.setData(uri);
Daniel Lehmannd02402c2012-05-14 18:30:53 -07001000 // Determine the type and also put that in the shortcut
1001 // (that can speed up launch a bit)
1002 newIntent.setDataAndType(uri, newIntent.resolveType(mContext));
Romain Guy7eb9e5e2009-12-02 20:10:07 -08001003
Daniel Lehmannc3a80402012-04-23 21:35:11 -07001004 final ContentValues values = new ContentValues();
1005 values.put(LauncherSettings.Favorites.INTENT,
1006 newIntent.toUri(0));
1007
1008 String updateWhere = Favorites._ID + "=" + favoriteId;
1009 db.update(TABLE_FAVORITES, values, updateWhere, null);
1010 }
Romain Guy7eb9e5e2009-12-02 20:10:07 -08001011 }
1012 } catch (RuntimeException ex) {
1013 Log.e(TAG, "Problem upgrading shortcut", ex);
1014 } catch (URISyntaxException e) {
Daniel Lehmannc3a80402012-04-23 21:35:11 -07001015 Log.e(TAG, "Problem upgrading shortcut", e);
Romain Guy7eb9e5e2009-12-02 20:10:07 -08001016 }
1017 }
1018 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -07001019
Romain Guy7eb9e5e2009-12-02 20:10:07 -08001020 db.setTransactionSuccessful();
1021 } catch (SQLException ex) {
1022 Log.w(TAG, "Problem while upgrading contacts", ex);
1023 return false;
1024 } finally {
1025 db.endTransaction();
1026 if (c != null) {
1027 c.close();
1028 }
1029 }
1030
1031 return true;
1032 }
1033
Joe Onorato0589f0f2010-02-08 13:44:00 -08001034 private void normalizeIcons(SQLiteDatabase db) {
1035 Log.d(TAG, "normalizing icons");
1036
Joe Onorato346e1292010-02-18 10:34:24 -05001037 db.beginTransaction();
Joe Onorato0589f0f2010-02-08 13:44:00 -08001038 Cursor c = null;
Joe Onorato9690b392010-03-23 17:34:37 -04001039 SQLiteStatement update = null;
Joe Onorato0589f0f2010-02-08 13:44:00 -08001040 try {
1041 boolean logged = false;
Joe Onorato9690b392010-03-23 17:34:37 -04001042 update = db.compileStatement("UPDATE favorites "
Jeff Hamiltoneaf77d62010-02-13 00:08:17 -06001043 + "SET icon=? WHERE _id=?");
Joe Onorato0589f0f2010-02-08 13:44:00 -08001044
1045 c = db.rawQuery("SELECT _id, icon FROM favorites WHERE iconType=" +
1046 Favorites.ICON_TYPE_BITMAP, null);
1047
1048 final int idIndex = c.getColumnIndexOrThrow(Favorites._ID);
1049 final int iconIndex = c.getColumnIndexOrThrow(Favorites.ICON);
1050
1051 while (c.moveToNext()) {
1052 long id = c.getLong(idIndex);
1053 byte[] data = c.getBlob(iconIndex);
1054 try {
1055 Bitmap bitmap = Utilities.resampleIconBitmap(
1056 BitmapFactory.decodeByteArray(data, 0, data.length),
1057 mContext);
1058 if (bitmap != null) {
1059 update.bindLong(1, id);
1060 data = ItemInfo.flattenBitmap(bitmap);
1061 if (data != null) {
1062 update.bindBlob(2, data);
1063 update.execute();
1064 }
1065 bitmap.recycle();
Joe Onorato0589f0f2010-02-08 13:44:00 -08001066 }
1067 } catch (Exception e) {
1068 if (!logged) {
1069 Log.e(TAG, "Failed normalizing icon " + id, e);
1070 } else {
1071 Log.e(TAG, "Also failed normalizing icon " + id);
1072 }
1073 logged = true;
1074 }
1075 }
Bjorn Bringert3a928e42010-02-19 11:15:40 +00001076 db.setTransactionSuccessful();
Joe Onorato0589f0f2010-02-08 13:44:00 -08001077 } catch (SQLException ex) {
1078 Log.w(TAG, "Problem while allocating appWidgetIds for existing widgets", ex);
1079 } finally {
1080 db.endTransaction();
Joe Onorato9690b392010-03-23 17:34:37 -04001081 if (update != null) {
1082 update.close();
1083 }
Joe Onorato0589f0f2010-02-08 13:44:00 -08001084 if (c != null) {
1085 c.close();
1086 }
1087 }
Michael Jurkaa8c760d2011-04-28 14:59:33 -07001088 }
1089
1090 // Generates a new ID to use for an object in your database. This method should be only
1091 // called from the main UI thread. As an exception, we do call it when we call the
1092 // constructor from the worker thread; however, this doesn't extend until after the
1093 // constructor is called, and we only pass a reference to LauncherProvider to LauncherApp
1094 // after that point
Sunny Goyal0fe505b2014-08-06 09:55:36 -07001095 @Override
Adam Cohendcd297f2013-06-18 13:13:40 -07001096 public long generateNewItemId() {
1097 if (mMaxItemId < 0) {
1098 throw new RuntimeException("Error: max item id was not initialized");
Michael Jurkaa8c760d2011-04-28 14:59:33 -07001099 }
Adam Cohendcd297f2013-06-18 13:13:40 -07001100 mMaxItemId += 1;
1101 return mMaxItemId;
Michael Jurkaa8c760d2011-04-28 14:59:33 -07001102 }
1103
Sunny Goyal0fe505b2014-08-06 09:55:36 -07001104 @Override
1105 public long insertAndCheck(SQLiteDatabase db, ContentValues values) {
1106 return dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values);
1107 }
1108
Winson Chungc763c4e2013-07-19 13:49:06 -07001109 public void updateMaxItemId(long id) {
1110 mMaxItemId = id + 1;
1111 }
1112
Chris Wren5dee7af2013-12-20 17:22:11 -05001113 public void checkId(String table, ContentValues values) {
1114 long id = values.getAsLong(LauncherSettings.BaseLauncherColumns._ID);
1115 if (table == LauncherProvider.TABLE_WORKSPACE_SCREENS) {
1116 mMaxScreenId = Math.max(id, mMaxScreenId);
1117 } else {
1118 mMaxItemId = Math.max(id, mMaxItemId);
1119 }
1120 }
1121
Adam Cohendcd297f2013-06-18 13:13:40 -07001122 private long initializeMaxItemId(SQLiteDatabase db) {
Michael Jurkaa8c760d2011-04-28 14:59:33 -07001123 Cursor c = db.rawQuery("SELECT MAX(_id) FROM favorites", null);
1124
1125 // get the result
1126 final int maxIdIndex = 0;
1127 long id = -1;
1128 if (c != null && c.moveToNext()) {
1129 id = c.getLong(maxIdIndex);
1130 }
Michael Jurka5130e402011-10-13 04:55:35 -07001131 if (c != null) {
1132 c.close();
1133 }
Michael Jurkaa8c760d2011-04-28 14:59:33 -07001134
1135 if (id == -1) {
Adam Cohendcd297f2013-06-18 13:13:40 -07001136 throw new RuntimeException("Error: could not query max item id");
1137 }
1138
1139 return id;
1140 }
1141
1142 // Generates a new ID to use for an workspace screen in your database. This method
1143 // should be only called from the main UI thread. As an exception, we do call it when we
1144 // call the constructor from the worker thread; however, this doesn't extend until after the
1145 // constructor is called, and we only pass a reference to LauncherProvider to LauncherApp
1146 // after that point
1147 public long generateNewScreenId() {
1148 if (mMaxScreenId < 0) {
1149 throw new RuntimeException("Error: max screen id was not initialized");
1150 }
1151 mMaxScreenId += 1;
Winson Chunga90303b2013-11-15 13:05:06 -08001152 // Log to disk
1153 Launcher.addDumpLog(TAG, "11683562 - generateNewScreenId(): " + mMaxScreenId, true);
Adam Cohendcd297f2013-06-18 13:13:40 -07001154 return mMaxScreenId;
1155 }
1156
1157 public void updateMaxScreenId(long maxScreenId) {
Winson Chunga90303b2013-11-15 13:05:06 -08001158 // Log to disk
1159 Launcher.addDumpLog(TAG, "11683562 - updateMaxScreenId(): " + maxScreenId, true);
Adam Cohendcd297f2013-06-18 13:13:40 -07001160 mMaxScreenId = maxScreenId;
1161 }
1162
1163 private long initializeMaxScreenId(SQLiteDatabase db) {
1164 Cursor c = db.rawQuery("SELECT MAX(" + LauncherSettings.WorkspaceScreens._ID + ") FROM " + TABLE_WORKSPACE_SCREENS, null);
1165
1166 // get the result
1167 final int maxIdIndex = 0;
1168 long id = -1;
1169 if (c != null && c.moveToNext()) {
1170 id = c.getLong(maxIdIndex);
1171 }
1172 if (c != null) {
1173 c.close();
1174 }
1175
1176 if (id == -1) {
1177 throw new RuntimeException("Error: could not query max screen id");
Michael Jurkaa8c760d2011-04-28 14:59:33 -07001178 }
1179
Winson Chunga90303b2013-11-15 13:05:06 -08001180 // Log to disk
1181 Launcher.addDumpLog(TAG, "11683562 - initializeMaxScreenId(): " + id, true);
Michael Jurkaa8c760d2011-04-28 14:59:33 -07001182 return id;
Joe Onorato0589f0f2010-02-08 13:44:00 -08001183 }
1184
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001185 /**
The Android Open Source Project7376fae2009-03-11 12:11:58 -07001186 * Upgrade existing clock and photo frame widgets into their new widget
Bjorn Bringert93c45762009-12-16 13:19:47 +00001187 * equivalents.
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001188 */
1189 private void convertWidgets(SQLiteDatabase db) {
Bjorn Bringert34251342009-12-15 13:33:11 +00001190 final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001191 final int[] bindSources = new int[] {
1192 Favorites.ITEM_TYPE_WIDGET_CLOCK,
1193 Favorites.ITEM_TYPE_WIDGET_PHOTO_FRAME,
Bjorn Bringert7984c942009-12-09 15:38:25 +00001194 Favorites.ITEM_TYPE_WIDGET_SEARCH,
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001195 };
Bjorn Bringert7984c942009-12-09 15:38:25 +00001196
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001197 final String selectWhere = buildOrWhereString(Favorites.ITEM_TYPE, bindSources);
Daniel Lehmannc3a80402012-04-23 21:35:11 -07001198
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001199 Cursor c = null;
Daniel Lehmannc3a80402012-04-23 21:35:11 -07001200
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001201 db.beginTransaction();
1202 try {
1203 // Select and iterate through each matching widget
Bjorn Bringert7984c942009-12-09 15:38:25 +00001204 c = db.query(TABLE_FAVORITES, new String[] { Favorites._ID, Favorites.ITEM_TYPE },
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001205 selectWhere, null, null, null, null);
Daniel Lehmannc3a80402012-04-23 21:35:11 -07001206
Joe Onoratoa30ce8e2009-11-11 08:16:49 -08001207 if (LOGD) Log.d(TAG, "found upgrade cursor count=" + c.getCount());
Daniel Lehmannc3a80402012-04-23 21:35:11 -07001208
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001209 final ContentValues values = new ContentValues();
1210 while (c != null && c.moveToNext()) {
1211 long favoriteId = c.getLong(0);
Bjorn Bringert7984c942009-12-09 15:38:25 +00001212 int favoriteType = c.getInt(1);
1213
The Android Open Source Project7376fae2009-03-11 12:11:58 -07001214 // Allocate and update database with new appWidgetId
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001215 try {
The Android Open Source Project7376fae2009-03-11 12:11:58 -07001216 int appWidgetId = mAppWidgetHost.allocateAppWidgetId();
Daniel Lehmannc3a80402012-04-23 21:35:11 -07001217
Joe Onoratoa30ce8e2009-11-11 08:16:49 -08001218 if (LOGD) {
1219 Log.d(TAG, "allocated appWidgetId=" + appWidgetId
1220 + " for favoriteId=" + favoriteId);
1221 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001222 values.clear();
Bjorn Bringert7984c942009-12-09 15:38:25 +00001223 values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_APPWIDGET);
1224 values.put(Favorites.APPWIDGET_ID, appWidgetId);
1225
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001226 // Original widgets might not have valid spans when upgrading
Bjorn Bringert7984c942009-12-09 15:38:25 +00001227 if (favoriteType == Favorites.ITEM_TYPE_WIDGET_SEARCH) {
1228 values.put(LauncherSettings.Favorites.SPANX, 4);
1229 values.put(LauncherSettings.Favorites.SPANY, 1);
1230 } else {
1231 values.put(LauncherSettings.Favorites.SPANX, 2);
1232 values.put(LauncherSettings.Favorites.SPANY, 2);
1233 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001234
1235 String updateWhere = Favorites._ID + "=" + favoriteId;
1236 db.update(TABLE_FAVORITES, values, updateWhere, null);
Bjorn Bringert34251342009-12-15 13:33:11 +00001237
Bjorn Bringert34251342009-12-15 13:33:11 +00001238 if (favoriteType == Favorites.ITEM_TYPE_WIDGET_CLOCK) {
Michael Jurka8b805b12012-04-18 14:23:14 -07001239 // TODO: check return value
1240 appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId,
Bjorn Bringert34251342009-12-15 13:33:11 +00001241 new ComponentName("com.android.alarmclock",
1242 "com.android.alarmclock.AnalogAppWidgetProvider"));
1243 } else if (favoriteType == Favorites.ITEM_TYPE_WIDGET_PHOTO_FRAME) {
Michael Jurka8b805b12012-04-18 14:23:14 -07001244 // TODO: check return value
1245 appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId,
Bjorn Bringert34251342009-12-15 13:33:11 +00001246 new ComponentName("com.android.camera",
1247 "com.android.camera.PhotoAppWidgetProvider"));
1248 } else if (favoriteType == Favorites.ITEM_TYPE_WIDGET_SEARCH) {
Michael Jurka8b805b12012-04-18 14:23:14 -07001249 // TODO: check return value
1250 appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId,
Bjorn Bringertcd8fec02010-01-14 13:26:43 +00001251 getSearchWidgetProvider());
Bjorn Bringert34251342009-12-15 13:33:11 +00001252 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001253 } catch (RuntimeException ex) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -08001254 Log.e(TAG, "Problem allocating appWidgetId", ex);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001255 }
1256 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -07001257
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001258 db.setTransactionSuccessful();
1259 } catch (SQLException ex) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -08001260 Log.w(TAG, "Problem while allocating appWidgetIds for existing widgets", ex);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001261 } finally {
1262 db.endTransaction();
1263 if (c != null) {
1264 c.close();
1265 }
1266 }
Winson Chungc763c4e2013-07-19 13:49:06 -07001267
1268 // Update max item id
1269 mMaxItemId = initializeMaxItemId(db);
1270 if (LOGD) Log.d(TAG, "mMaxItemId: " + mMaxItemId);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001271 }
1272
Adam Cohena043fa82014-07-23 14:49:38 -07001273 private boolean initializeExternalAdd(ContentValues values) {
1274 // 1. Ensure that externally added items have a valid item id
1275 long id = generateNewItemId();
1276 values.put(LauncherSettings.Favorites._ID, id);
1277
1278 // 2. In the case of an app widget, and if no app widget id is specified, we
1279 // attempt allocate and bind the widget.
1280 Integer itemType = values.getAsInteger(LauncherSettings.Favorites.ITEM_TYPE);
1281 if (itemType != null &&
1282 itemType.intValue() == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET &&
1283 !values.containsKey(LauncherSettings.Favorites.APPWIDGET_ID)) {
1284
1285 final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext);
1286 ComponentName cn = ComponentName.unflattenFromString(
1287 values.getAsString(Favorites.APPWIDGET_PROVIDER));
1288
1289 if (cn != null) {
1290 try {
1291 int appWidgetId = mAppWidgetHost.allocateAppWidgetId();
Adam Cohen3ed316a2014-07-23 18:21:20 -07001292 values.put(LauncherSettings.Favorites.APPWIDGET_ID, appWidgetId);
1293 if (appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId,cn)) {
1294 return true;
Adam Cohena043fa82014-07-23 14:49:38 -07001295 }
1296 } catch (RuntimeException e) {
1297 Log.e(TAG, "Failed to initialize external widget", e);
Adam Cohena043fa82014-07-23 14:49:38 -07001298 }
1299 }
Adam Cohen3ed316a2014-07-23 18:21:20 -07001300 return false;
Adam Cohena043fa82014-07-23 14:49:38 -07001301 }
Adam Cohen7ec3bbf2014-07-31 00:09:45 -07001302
1303 // Add screen id if not present
1304 long screenId = values.getAsLong(LauncherSettings.Favorites.SCREEN);
1305 if (!addScreenIdIfNecessary(screenId)) {
1306 return false;
1307 }
Adam Cohena043fa82014-07-23 14:49:38 -07001308 return true;
1309 }
1310
Adam Cohen7ec3bbf2014-07-31 00:09:45 -07001311 // Returns true of screen id exists, or if successfully added
1312 private boolean addScreenIdIfNecessary(long screenId) {
1313 if (!hasScreenId(screenId)) {
1314 int rank = getMaxScreenRank() + 1;
1315
1316 ContentValues v = new ContentValues();
1317 v.put(LauncherSettings.WorkspaceScreens._ID, screenId);
1318 v.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, rank);
1319 if (dbInsertAndCheck(this, getWritableDatabase(),
1320 TABLE_WORKSPACE_SCREENS, null, v) < 0) {
1321 return false;
1322 }
1323 }
1324 return true;
1325 }
1326
1327 private boolean hasScreenId(long screenId) {
1328 SQLiteDatabase db = getWritableDatabase();
1329 Cursor c = db.rawQuery("SELECT * FROM " + TABLE_WORKSPACE_SCREENS + " WHERE "
1330 + LauncherSettings.WorkspaceScreens._ID + " = " + screenId, null);
1331 if (c != null) {
1332 int count = c.getCount();
1333 c.close();
1334 return count > 0;
1335 } else {
1336 return false;
1337 }
1338 }
1339
1340 private int getMaxScreenRank() {
1341 SQLiteDatabase db = getWritableDatabase();
1342 Cursor c = db.rawQuery("SELECT MAX(" + LauncherSettings.WorkspaceScreens.SCREEN_RANK
1343 + ") FROM " + TABLE_WORKSPACE_SCREENS, null);
1344
1345 // get the result
1346 final int maxRankIndex = 0;
1347 int rank = -1;
1348 if (c != null && c.moveToNext()) {
1349 rank = c.getInt(maxRankIndex);
1350 }
1351 if (c != null) {
1352 c.close();
1353 }
1354
1355 return rank;
1356 }
1357
Michael Jurka8b805b12012-04-18 14:23:14 -07001358 private static final void beginDocument(XmlPullParser parser, String firstElementName)
1359 throws XmlPullParserException, IOException {
1360 int type;
Michael Jurka9bc8eba2012-05-21 20:36:44 -07001361 while ((type = parser.next()) != XmlPullParser.START_TAG
1362 && type != XmlPullParser.END_DOCUMENT) {
Michael Jurka8b805b12012-04-18 14:23:14 -07001363 ;
1364 }
1365
Michael Jurka9bc8eba2012-05-21 20:36:44 -07001366 if (type != XmlPullParser.START_TAG) {
Michael Jurka8b805b12012-04-18 14:23:14 -07001367 throw new XmlPullParserException("No start tag found");
1368 }
1369
1370 if (!parser.getName().equals(firstElementName)) {
1371 throw new XmlPullParserException("Unexpected start tag: found " + parser.getName() +
1372 ", expected " + firstElementName);
1373 }
1374 }
1375
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001376 private static Intent buildMainIntent() {
1377 Intent intent = new Intent(Intent.ACTION_MAIN, null);
1378 intent.addCategory(Intent.CATEGORY_LAUNCHER);
1379 return intent;
1380 }
1381
Sunny Goyal0fe505b2014-08-06 09:55:36 -07001382 private int loadFavorites(SQLiteDatabase db, WorkspaceLoader loader) {
Adam Cohen71483f42014-05-15 14:04:01 -07001383 ArrayList<Long> screenIds = new ArrayList<Long>();
Sunny Goyal0fe505b2014-08-06 09:55:36 -07001384 // TODO: Use multiple loaders with fall-back and transaction.
1385 int count = loader.loadLayout(db, screenIds);
Adam Cohen71483f42014-05-15 14:04:01 -07001386
1387 // Add the screens specified by the items above
1388 Collections.sort(screenIds);
1389 int rank = 0;
1390 ContentValues values = new ContentValues();
1391 for (Long id : screenIds) {
1392 values.clear();
1393 values.put(LauncherSettings.WorkspaceScreens._ID, id);
1394 values.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, rank);
1395 if (dbInsertAndCheck(this, db, TABLE_WORKSPACE_SCREENS, null, values) < 0) {
1396 throw new RuntimeException("Failed initialize screen table"
1397 + "from default layout");
1398 }
1399 rank++;
1400 }
1401
1402 // Ensure that the max ids are initialized
1403 mMaxItemId = initializeMaxItemId(db);
1404 mMaxScreenId = initializeMaxScreenId(db);
Adam Cohen7ec3bbf2014-07-31 00:09:45 -07001405
Adam Cohen71483f42014-05-15 14:04:01 -07001406 return count;
1407 }
1408
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001409 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001410 * Loads the default set of favorite packages from an xml file.
1411 *
1412 * @param db The database to write the values into
Winson Chung3d503fb2011-07-13 17:25:49 -07001413 * @param filterContainerId The specific container id of items to load
Adam Cohen71483f42014-05-15 14:04:01 -07001414 * @param the set of screenIds which are used by the favorites
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001415 */
Adam Cohen9b8f51f2014-05-30 15:34:09 -07001416 private int loadFavoritesRecursive(SQLiteDatabase db, Resources res, int workspaceResourceId,
Adam Cohen71483f42014-05-15 14:04:01 -07001417 ArrayList<Long> screenIds) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001418
Adam Cohen71483f42014-05-15 14:04:01 -07001419 ContentValues values = new ContentValues();
Daniel Sandler57dac262013-10-03 13:28:36 -04001420 if (LOGD) Log.v(TAG, String.format("Loading favorites from resid=0x%08x", workspaceResourceId));
1421
Adam Cohen71483f42014-05-15 14:04:01 -07001422 int count = 0;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001423 try {
Adam Cohen9b8f51f2014-05-30 15:34:09 -07001424 XmlResourceParser parser = res.getXml(workspaceResourceId);
Michael Jurka8b805b12012-04-18 14:23:14 -07001425 beginDocument(parser, TAG_FAVORITES);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001426
The Android Open Source Projectf96811c2009-03-18 17:39:48 -07001427 final int depth = parser.getDepth();
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001428
The Android Open Source Projectf96811c2009-03-18 17:39:48 -07001429 int type;
1430 while (((type = parser.next()) != XmlPullParser.END_TAG ||
1431 parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
1432
1433 if (type != XmlPullParser.START_TAG) {
1434 continue;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001435 }
1436
The Android Open Source Projectf96811c2009-03-18 17:39:48 -07001437 boolean added = false;
1438 final String name = parser.getName();
1439
Daniel Sandler57dac262013-10-03 13:28:36 -04001440 if (TAG_INCLUDE.equals(name)) {
Daniel Sandler57dac262013-10-03 13:28:36 -04001441
Adam Cohen9b8f51f2014-05-30 15:34:09 -07001442 final int resId = getAttributeResourceValue(parser, ATTR_WORKSPACE, 0);
Daniel Sandler57dac262013-10-03 13:28:36 -04001443
1444 if (LOGD) Log.v(TAG, String.format(("%" + (2*(depth+1)) + "s<include workspace=%08x>"),
1445 "", resId));
1446
1447 if (resId != 0 && resId != workspaceResourceId) {
1448 // recursively load some more favorites, why not?
Adam Cohen9b8f51f2014-05-30 15:34:09 -07001449 count += loadFavoritesRecursive(db, res, resId, screenIds);
Daniel Sandler57dac262013-10-03 13:28:36 -04001450 added = false;
Daniel Sandler57dac262013-10-03 13:28:36 -04001451 } else {
1452 Log.w(TAG, String.format("Skipping <include workspace=0x%08x>", resId));
1453 }
1454
Daniel Sandler57dac262013-10-03 13:28:36 -04001455 if (LOGD) Log.v(TAG, String.format(("%" + (2*(depth+1)) + "s</include>"), ""));
1456 continue;
1457 }
1458
1459 // Assuming it's a <favorite> at this point
Winson Chung3d503fb2011-07-13 17:25:49 -07001460 long container = LauncherSettings.Favorites.CONTAINER_DESKTOP;
Adam Cohen9b8f51f2014-05-30 15:34:09 -07001461 String strContainer = getAttributeValue(parser, ATTR_CONTAINER);
1462 if (strContainer != null) {
1463 container = Long.valueOf(strContainer);
Winson Chung3d503fb2011-07-13 17:25:49 -07001464 }
The Android Open Source Projectf96811c2009-03-18 17:39:48 -07001465
Adam Cohen9b8f51f2014-05-30 15:34:09 -07001466 String screen = getAttributeValue(parser, ATTR_SCREEN);
1467 String x = getAttributeValue(parser, ATTR_X);
1468 String y = getAttributeValue(parser, ATTR_Y);
Winson Chung6d092682011-11-16 18:43:26 -08001469
Winson Chung6d092682011-11-16 18:43:26 -08001470 values.clear();
1471 values.put(LauncherSettings.Favorites.CONTAINER, container);
1472 values.put(LauncherSettings.Favorites.SCREEN, screen);
1473 values.put(LauncherSettings.Favorites.CELLX, x);
1474 values.put(LauncherSettings.Favorites.CELLY, y);
1475
Daniel Sandler57dac262013-10-03 13:28:36 -04001476 if (LOGD) {
Adam Cohen9b8f51f2014-05-30 15:34:09 -07001477 final String title = getAttributeValue(parser, ATTR_TITLE);
1478 final String pkg = getAttributeValue(parser, ATTR_PACKAGE_NAME);
Daniel Sandler57dac262013-10-03 13:28:36 -04001479 final String something = title != null ? title : pkg;
1480 Log.v(TAG, String.format(
1481 ("%" + (2*(depth+1)) + "s<%s%s c=%d s=%s x=%s y=%s>"),
1482 "", name,
1483 (something == null ? "" : (" \"" + something + "\"")),
1484 container, screen, x, y));
1485 }
1486
Winson Chung6d092682011-11-16 18:43:26 -08001487 if (TAG_FAVORITE.equals(name)) {
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001488 long id = addAppShortcut(db, values, parser);
Winson Chung6d092682011-11-16 18:43:26 -08001489 added = id >= 0;
Winson Chung6d092682011-11-16 18:43:26 -08001490 } else if (TAG_APPWIDGET.equals(name)) {
Adam Cohen9b8f51f2014-05-30 15:34:09 -07001491 added = addAppWidget(parser, type, db, values);
Winson Chung6d092682011-11-16 18:43:26 -08001492 } else if (TAG_SHORTCUT.equals(name)) {
Adam Cohen9b8f51f2014-05-30 15:34:09 -07001493 long id = addUriShortcut(db, values, res, parser);
Winson Chung6d092682011-11-16 18:43:26 -08001494 added = id >= 0;
Jason Monk41314972014-03-03 16:11:30 -05001495 } else if (TAG_RESOLVE.equals(name)) {
1496 // This looks through the contained favorites (or meta-favorites) and
1497 // attempts to add them as shortcuts in the fallback group's location
1498 // until one is added successfully.
1499 added = false;
1500 final int groupDepth = parser.getDepth();
1501 while ((type = parser.next()) != XmlPullParser.END_TAG ||
1502 parser.getDepth() > groupDepth) {
1503 if (type != XmlPullParser.START_TAG) {
1504 continue;
1505 }
1506 final String fallback_item_name = parser.getName();
Jason Monk41314972014-03-03 16:11:30 -05001507 if (!added) {
1508 if (TAG_FAVORITE.equals(fallback_item_name)) {
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001509 final long id = addAppShortcut(db, values, parser);
Jason Monk41314972014-03-03 16:11:30 -05001510 added = id >= 0;
1511 } else {
Adam Cohen9b8f51f2014-05-30 15:34:09 -07001512 Log.e(TAG, "Fallback groups can contain only favorites, found "
1513 + fallback_item_name);
Jason Monk41314972014-03-03 16:11:30 -05001514 }
1515 }
Jason Monk41314972014-03-03 16:11:30 -05001516 }
Winson Chung6d092682011-11-16 18:43:26 -08001517 } else if (TAG_FOLDER.equals(name)) {
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001518 // Folder contents are nested in this XML file
Adam Cohen43f3ca02014-07-25 13:24:09 -07001519 added = loadFolder(db, values, res, parser);
Winson Chung3d503fb2011-07-13 17:25:49 -07001520
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001521 } else if (TAG_PARTNER_FOLDER.equals(name)) {
1522 // Folder contents come from an external XML resource
1523 final Partner partner = Partner.get(mPackageManager);
1524 if (partner != null) {
1525 final Resources partnerRes = partner.getResources();
Adam Cohen4ae96ce2014-08-29 15:05:48 -07001526 final int resId = partnerRes.getIdentifier(Partner.RES_FOLDER,
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001527 "xml", partner.getPackageName());
1528 if (resId != 0) {
1529 final XmlResourceParser partnerParser = partnerRes.getXml(resId);
1530 beginDocument(partnerParser, TAG_FOLDER);
1531 added = loadFolder(db, values, partnerRes, partnerParser);
Winson Chung6d092682011-11-16 18:43:26 -08001532 }
Winson Chung3d503fb2011-07-13 17:25:49 -07001533 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001534 }
Adam Cohen71483f42014-05-15 14:04:01 -07001535 if (added) {
1536 long screenId = Long.parseLong(screen);
1537 // Keep track of the set of screens which need to be added to the db.
1538 if (!screenIds.contains(screenId) &&
1539 container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
1540 screenIds.add(screenId);
1541 }
1542 count++;
1543 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001544 }
1545 } catch (XmlPullParserException e) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -08001546 Log.w(TAG, "Got exception parsing favorites.", e);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001547 } catch (IOException e) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -08001548 Log.w(TAG, "Got exception parsing favorites.", e);
Winson Chung3d503fb2011-07-13 17:25:49 -07001549 } catch (RuntimeException e) {
1550 Log.w(TAG, "Got exception parsing favorites.", e);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001551 }
Adam Cohen71483f42014-05-15 14:04:01 -07001552 return count;
The Android Open Source Projectf96811c2009-03-18 17:39:48 -07001553 }
1554
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001555 /**
1556 * Parse folder starting at current {@link XmlPullParser} location.
1557 */
1558 private boolean loadFolder(SQLiteDatabase db, ContentValues values, Resources res,
1559 XmlResourceParser parser) throws IOException, XmlPullParserException {
1560 final String title;
1561 final int titleResId = getAttributeResourceValue(parser, ATTR_TITLE, 0);
1562 if (titleResId != 0) {
1563 title = res.getString(titleResId);
1564 } else {
1565 title = mContext.getResources().getString(R.string.folder_name);
1566 }
1567
1568 values.put(LauncherSettings.Favorites.TITLE, title);
1569 long folderId = addFolder(db, values);
1570 boolean added = folderId >= 0;
1571
1572 ArrayList<Long> folderItems = new ArrayList<Long>();
1573
1574 int type;
1575 int folderDepth = parser.getDepth();
1576 while ((type = parser.next()) != XmlPullParser.END_TAG ||
1577 parser.getDepth() > folderDepth) {
1578 if (type != XmlPullParser.START_TAG) {
1579 continue;
1580 }
1581 final String tag = parser.getName();
1582
1583 final ContentValues childValues = new ContentValues();
1584 childValues.put(LauncherSettings.Favorites.CONTAINER, folderId);
1585
1586 if (LOGD) {
1587 final String pkg = getAttributeValue(parser, ATTR_PACKAGE_NAME);
1588 final String uri = getAttributeValue(parser, ATTR_URI);
1589 Log.v(TAG, String.format(("%" + (2*(folderDepth+1)) + "s<%s \"%s\">"), "",
1590 tag, uri != null ? uri : pkg));
1591 }
1592
1593 if (TAG_FAVORITE.equals(tag) && folderId >= 0) {
1594 final long id = addAppShortcut(db, childValues, parser);
1595 if (id >= 0) {
1596 folderItems.add(id);
1597 }
1598 } else if (TAG_SHORTCUT.equals(tag) && folderId >= 0) {
1599 final long id = addUriShortcut(db, childValues, res, parser);
1600 if (id >= 0) {
1601 folderItems.add(id);
1602 }
1603 } else {
1604 throw new RuntimeException("Folders can contain only shortcuts");
1605 }
1606 }
1607
1608 // We can only have folders with >= 2 items, so we need to remove the
1609 // folder and clean up if less than 2 items were included, or some
1610 // failed to add, and less than 2 were actually added
1611 if (folderItems.size() < 2 && folderId >= 0) {
1612 // Delete the folder
1613 deleteId(db, folderId);
1614
1615 // If we have a single item, promote it to where the folder
1616 // would have been.
1617 if (folderItems.size() == 1) {
1618 final ContentValues childValues = new ContentValues();
1619 copyInteger(values, childValues, LauncherSettings.Favorites.CONTAINER);
1620 copyInteger(values, childValues, LauncherSettings.Favorites.SCREEN);
1621 copyInteger(values, childValues, LauncherSettings.Favorites.CELLX);
1622 copyInteger(values, childValues, LauncherSettings.Favorites.CELLY);
1623
1624 final long id = folderItems.get(0);
1625 db.update(TABLE_FAVORITES, childValues,
1626 LauncherSettings.Favorites._ID + "=" + id, null);
1627 } else {
1628 added = false;
1629 }
1630 }
1631 return added;
1632 }
1633
Jason Monk41314972014-03-03 16:11:30 -05001634 // A meta shortcut attempts to resolve an intent specified as a URI in the XML, if a
1635 // logical choice for what shortcut should be used for that intent exists, then it is
1636 // added. Otherwise add nothing.
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001637 private long addAppShortcutByUri(SQLiteDatabase db, ContentValues values,
1638 String intentUri) {
Jason Monk41314972014-03-03 16:11:30 -05001639 Intent metaIntent;
The Android Open Source Projectf96811c2009-03-18 17:39:48 -07001640 try {
Jason Monk41314972014-03-03 16:11:30 -05001641 metaIntent = Intent.parseUri(intentUri, 0);
1642 } catch (URISyntaxException e) {
1643 Log.e(TAG, "Unable to add meta-favorite: " + intentUri, e);
1644 return -1;
1645 }
1646
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001647 ResolveInfo resolved = mPackageManager.resolveActivity(metaIntent,
Jason Monk41314972014-03-03 16:11:30 -05001648 PackageManager.MATCH_DEFAULT_ONLY);
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001649 final List<ResolveInfo> appList = mPackageManager.queryIntentActivities(
Jason Monk41314972014-03-03 16:11:30 -05001650 metaIntent, PackageManager.MATCH_DEFAULT_ONLY);
1651
1652 // Verify that the result is an app and not just the resolver dialog asking which
1653 // app to use.
1654 if (wouldLaunchResolverActivity(resolved, appList)) {
1655 // If only one of the results is a system app then choose that as the default.
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001656 final ResolveInfo systemApp = getSingleSystemActivity(appList);
Jason Monk41314972014-03-03 16:11:30 -05001657 if (systemApp == null) {
1658 // There is no logical choice for this meta-favorite, so rather than making
1659 // a bad choice just add nothing.
1660 Log.w(TAG, "No preference or single system activity found for "
1661 + metaIntent.toString());
Adam Cohen228da5a2011-07-27 22:23:47 -07001662 return -1;
1663 }
Jason Monk41314972014-03-03 16:11:30 -05001664 resolved = systemApp;
The Android Open Source Projectf96811c2009-03-18 17:39:48 -07001665 }
Jason Monk41314972014-03-03 16:11:30 -05001666 final ActivityInfo info = resolved.activityInfo;
Jason Monkc3009c02014-08-20 09:41:21 -04001667 final Intent intent = mPackageManager.getLaunchIntentForPackage(info.packageName);
1668 if (intent == null) {
1669 return -1;
1670 }
Jason Monk41314972014-03-03 16:11:30 -05001671 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
1672 Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
1673
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001674 return addAppShortcut(db, values, info.loadLabel(mPackageManager).toString(), intent);
Jason Monk41314972014-03-03 16:11:30 -05001675 }
1676
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001677 private ResolveInfo getSingleSystemActivity(List<ResolveInfo> appList) {
Jason Monk41314972014-03-03 16:11:30 -05001678 ResolveInfo systemResolve = null;
1679 final int N = appList.size();
1680 for (int i = 0; i < N; ++i) {
1681 try {
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001682 ApplicationInfo info = mPackageManager.getApplicationInfo(
Jason Monk41314972014-03-03 16:11:30 -05001683 appList.get(i).activityInfo.packageName, 0);
1684 if ((info.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
1685 if (systemResolve != null) {
1686 return null;
1687 } else {
1688 systemResolve = appList.get(i);
1689 }
1690 }
1691 } catch (PackageManager.NameNotFoundException e) {
1692 Log.w(TAG, "Unable to get info about resolve results", e);
1693 return null;
1694 }
1695 }
1696 return systemResolve;
1697 }
1698
1699 private boolean wouldLaunchResolverActivity(ResolveInfo resolved,
1700 List<ResolveInfo> appList) {
1701 // If the list contains the above resolved activity, then it can't be
1702 // ResolverActivity itself.
1703 for (int i = 0; i < appList.size(); ++i) {
1704 ResolveInfo tmp = appList.get(i);
1705 if (tmp.activityInfo.name.equals(resolved.activityInfo.name)
1706 && tmp.activityInfo.packageName.equals(resolved.activityInfo.packageName)) {
1707 return false;
1708 }
1709 }
1710 return true;
1711 }
1712
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001713 private long addAppShortcut(SQLiteDatabase db, ContentValues values,
1714 XmlResourceParser parser) {
1715 final String packageName = getAttributeValue(parser, ATTR_PACKAGE_NAME);
1716 final String className = getAttributeValue(parser, ATTR_CLASS_NAME);
1717 final String uri = getAttributeValue(parser, ATTR_URI);
1718
1719 if (!TextUtils.isEmpty(packageName) && !TextUtils.isEmpty(className)) {
Jason Monk41314972014-03-03 16:11:30 -05001720 ActivityInfo info;
Jason Monk41314972014-03-03 16:11:30 -05001721 try {
1722 ComponentName cn;
1723 try {
1724 cn = new ComponentName(packageName, className);
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001725 info = mPackageManager.getActivityInfo(cn, 0);
Jason Monk41314972014-03-03 16:11:30 -05001726 } catch (PackageManager.NameNotFoundException nnfe) {
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001727 String[] packages = mPackageManager.currentToCanonicalPackageNames(
Jason Monk41314972014-03-03 16:11:30 -05001728 new String[] { packageName });
1729 cn = new ComponentName(packages[0], className);
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001730 info = mPackageManager.getActivityInfo(cn, 0);
Jason Monk41314972014-03-03 16:11:30 -05001731 }
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001732 final Intent intent = buildMainIntent();
Jason Monk41314972014-03-03 16:11:30 -05001733 intent.setComponent(cn);
1734 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
1735 Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
1736
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001737 return addAppShortcut(db, values, info.loadLabel(mPackageManager).toString(),
Jason Monk41314972014-03-03 16:11:30 -05001738 intent);
1739 } catch (PackageManager.NameNotFoundException e) {
1740 Log.w(TAG, "Unable to add favorite: " + packageName +
1741 "/" + className, e);
1742 }
1743 return -1;
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001744 } else if (!TextUtils.isEmpty(uri)) {
Jason Monk41314972014-03-03 16:11:30 -05001745 // If no component specified try to find a shortcut to add from the URI.
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001746 return addAppShortcutByUri(db, values, uri);
Jason Monk41314972014-03-03 16:11:30 -05001747 } else {
1748 Log.e(TAG, "Skipping invalid <favorite> with no component or uri");
1749 return -1;
1750 }
1751 }
1752
1753 private long addAppShortcut(SQLiteDatabase db, ContentValues values, String title,
1754 Intent intent) {
1755 long id = generateNewItemId();
1756 values.put(Favorites.INTENT, intent.toUri(0));
1757 values.put(Favorites.TITLE, title);
1758 values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_APPLICATION);
1759 values.put(Favorites.SPANX, 1);
1760 values.put(Favorites.SPANY, 1);
1761 values.put(Favorites._ID, id);
1762 if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values) < 0) {
1763 return -1;
1764 } else {
1765 return id;
1766 }
Adam Cohen228da5a2011-07-27 22:23:47 -07001767 }
1768
1769 private long addFolder(SQLiteDatabase db, ContentValues values) {
1770 values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_FOLDER);
1771 values.put(Favorites.SPANX, 1);
1772 values.put(Favorites.SPANY, 1);
Adam Cohendcd297f2013-06-18 13:13:40 -07001773 long id = generateNewItemId();
Adam Cohen228da5a2011-07-27 22:23:47 -07001774 values.put(Favorites._ID, id);
1775 if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values) <= 0) {
1776 return -1;
1777 } else {
1778 return id;
1779 }
The Android Open Source Projectf96811c2009-03-18 17:39:48 -07001780 }
1781
Bjorn Bringertcd8fec02010-01-14 13:26:43 +00001782 private ComponentName getSearchWidgetProvider() {
1783 SearchManager searchManager =
1784 (SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE);
1785 ComponentName searchComponent = searchManager.getGlobalSearchActivity();
1786 if (searchComponent == null) return null;
1787 return getProviderInPackage(searchComponent.getPackageName());
1788 }
1789
1790 /**
1791 * Gets an appwidget provider from the given package. If the package contains more than
1792 * one appwidget provider, an arbitrary one is returned.
1793 */
1794 private ComponentName getProviderInPackage(String packageName) {
1795 AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext);
1796 List<AppWidgetProviderInfo> providers = appWidgetManager.getInstalledProviders();
1797 if (providers == null) return null;
1798 final int providerCount = providers.size();
1799 for (int i = 0; i < providerCount; i++) {
1800 ComponentName provider = providers.get(i).provider;
1801 if (provider != null && provider.getPackageName().equals(packageName)) {
1802 return provider;
1803 }
1804 }
1805 return null;
1806 }
1807
Adam Cohen9b8f51f2014-05-30 15:34:09 -07001808 private boolean addAppWidget(XmlResourceParser parser, int type,
1809 SQLiteDatabase db, ContentValues values)
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001810 throws XmlPullParserException, IOException {
Romain Guy693599f2010-03-23 10:58:18 -07001811
Adam Cohen9b8f51f2014-05-30 15:34:09 -07001812 String packageName = getAttributeValue(parser, ATTR_PACKAGE_NAME);
1813 String className = getAttributeValue(parser, ATTR_CLASS_NAME);
Mike Cleronb87bd162009-10-30 16:36:56 -07001814
1815 if (packageName == null || className == null) {
1816 return false;
1817 }
Romain Guy693599f2010-03-23 10:58:18 -07001818
1819 boolean hasPackage = true;
Mike Cleronb87bd162009-10-30 16:36:56 -07001820 ComponentName cn = new ComponentName(packageName, className);
Romain Guy693599f2010-03-23 10:58:18 -07001821 try {
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001822 mPackageManager.getReceiverInfo(cn, 0);
Romain Guy693599f2010-03-23 10:58:18 -07001823 } catch (Exception e) {
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001824 String[] packages = mPackageManager.currentToCanonicalPackageNames(
Romain Guy693599f2010-03-23 10:58:18 -07001825 new String[] { packageName });
1826 cn = new ComponentName(packages[0], className);
1827 try {
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001828 mPackageManager.getReceiverInfo(cn, 0);
Romain Guy693599f2010-03-23 10:58:18 -07001829 } catch (Exception e1) {
Adam Cohen9b8f51f2014-05-30 15:34:09 -07001830 System.out.println("Can't find widget provider: " + className);
Romain Guy693599f2010-03-23 10:58:18 -07001831 hasPackage = false;
1832 }
1833 }
1834
1835 if (hasPackage) {
Adam Cohen9b8f51f2014-05-30 15:34:09 -07001836 String spanX = getAttributeValue(parser, ATTR_SPAN_X);
1837 String spanY = getAttributeValue(parser, ATTR_SPAN_Y);
1838
1839 values.put(Favorites.SPANX, spanX);
1840 values.put(Favorites.SPANY, spanY);
Winson Chungb3302ae2012-05-01 10:19:14 -07001841
1842 // Read the extras
1843 Bundle extras = new Bundle();
1844 int widgetDepth = parser.getDepth();
1845 while ((type = parser.next()) != XmlPullParser.END_TAG ||
1846 parser.getDepth() > widgetDepth) {
1847 if (type != XmlPullParser.START_TAG) {
1848 continue;
1849 }
1850
Winson Chungb3302ae2012-05-01 10:19:14 -07001851 if (TAG_EXTRA.equals(parser.getName())) {
Adam Cohen9b8f51f2014-05-30 15:34:09 -07001852 String key = getAttributeValue(parser, ATTR_KEY);
1853 String value = getAttributeValue(parser, ATTR_VALUE);
Winson Chungb3302ae2012-05-01 10:19:14 -07001854 if (key != null && value != null) {
1855 extras.putString(key, value);
1856 } else {
1857 throw new RuntimeException("Widget extras must have a key and value");
1858 }
1859 } else {
1860 throw new RuntimeException("Widgets can contain only extras");
1861 }
Winson Chungb3302ae2012-05-01 10:19:14 -07001862 }
1863
Adam Cohen9b8f51f2014-05-30 15:34:09 -07001864 return addAppWidget(db, values, cn, extras);
Romain Guy693599f2010-03-23 10:58:18 -07001865 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -07001866
Romain Guy693599f2010-03-23 10:58:18 -07001867 return false;
Bjorn Bringert7984c942009-12-09 15:38:25 +00001868 }
Adam Cohena043fa82014-07-23 14:49:38 -07001869
Bjorn Bringert7984c942009-12-09 15:38:25 +00001870 private boolean addAppWidget(SQLiteDatabase db, ContentValues values, ComponentName cn,
Adam Cohen9b8f51f2014-05-30 15:34:09 -07001871 Bundle extras) {
Mike Cleronb87bd162009-10-30 16:36:56 -07001872 boolean allocatedAppWidgets = false;
1873 final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext);
1874
1875 try {
1876 int appWidgetId = mAppWidgetHost.allocateAppWidgetId();
Daniel Lehmannc3a80402012-04-23 21:35:11 -07001877
Mike Cleronb87bd162009-10-30 16:36:56 -07001878 values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_APPWIDGET);
Mike Cleronb87bd162009-10-30 16:36:56 -07001879 values.put(Favorites.APPWIDGET_ID, appWidgetId);
Chris Wrend5e66bf2013-09-16 14:02:29 -04001880 values.put(Favorites.APPWIDGET_PROVIDER, cn.flattenToString());
Adam Cohendcd297f2013-06-18 13:13:40 -07001881 values.put(Favorites._ID, generateNewItemId());
Michael Jurkaa8c760d2011-04-28 14:59:33 -07001882 dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values);
Mike Cleronb87bd162009-10-30 16:36:56 -07001883
1884 allocatedAppWidgets = true;
Daniel Lehmannc3a80402012-04-23 21:35:11 -07001885
Michael Jurka8b805b12012-04-18 14:23:14 -07001886 // TODO: need to check return value
1887 appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, cn);
Winson Chungb3302ae2012-05-01 10:19:14 -07001888
1889 // Send a broadcast to configure the widget
1890 if (extras != null && !extras.isEmpty()) {
1891 Intent intent = new Intent(ACTION_APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE);
1892 intent.setComponent(cn);
1893 intent.putExtras(extras);
1894 intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
1895 mContext.sendBroadcast(intent);
1896 }
Mike Cleronb87bd162009-10-30 16:36:56 -07001897 } catch (RuntimeException ex) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -08001898 Log.e(TAG, "Problem allocating appWidgetId", ex);
Mike Cleronb87bd162009-10-30 16:36:56 -07001899 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -07001900
Mike Cleronb87bd162009-10-30 16:36:56 -07001901 return allocatedAppWidgets;
1902 }
Adam Cohen228da5a2011-07-27 22:23:47 -07001903
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001904 private long addUriShortcut(SQLiteDatabase db, ContentValues values, Resources res,
1905 XmlResourceParser parser) {
1906 final int iconResId = getAttributeResourceValue(parser, ATTR_ICON, 0);
1907 final int titleResId = getAttributeResourceValue(parser, ATTR_TITLE, 0);
Mike Cleronb87bd162009-10-30 16:36:56 -07001908
Romain Guy7eb9e5e2009-12-02 20:10:07 -08001909 Intent intent;
Mike Cleronb87bd162009-10-30 16:36:56 -07001910 String uri = null;
1911 try {
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001912 uri = getAttributeValue(parser, ATTR_URI);
Mike Cleronb87bd162009-10-30 16:36:56 -07001913 intent = Intent.parseUri(uri, 0);
1914 } catch (URISyntaxException e) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -08001915 Log.w(TAG, "Shortcut has malformed uri: " + uri);
Adam Cohen228da5a2011-07-27 22:23:47 -07001916 return -1; // Oh well
Mike Cleronb87bd162009-10-30 16:36:56 -07001917 }
1918
1919 if (iconResId == 0 || titleResId == 0) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -08001920 Log.w(TAG, "Shortcut is missing title or icon resource ID");
Adam Cohen228da5a2011-07-27 22:23:47 -07001921 return -1;
Mike Cleronb87bd162009-10-30 16:36:56 -07001922 }
1923
Adam Cohendcd297f2013-06-18 13:13:40 -07001924 long id = generateNewItemId();
Mike Cleronb87bd162009-10-30 16:36:56 -07001925 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1926 values.put(Favorites.INTENT, intent.toUri(0));
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001927 values.put(Favorites.TITLE, res.getString(titleResId));
Mike Cleronb87bd162009-10-30 16:36:56 -07001928 values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_SHORTCUT);
1929 values.put(Favorites.SPANX, 1);
1930 values.put(Favorites.SPANY, 1);
1931 values.put(Favorites.ICON_TYPE, Favorites.ICON_TYPE_RESOURCE);
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001932 values.put(Favorites.ICON_PACKAGE, res.getResourcePackageName(iconResId));
1933 values.put(Favorites.ICON_RESOURCE, res.getResourceName(iconResId));
Adam Cohen228da5a2011-07-27 22:23:47 -07001934 values.put(Favorites._ID, id);
Mike Cleronb87bd162009-10-30 16:36:56 -07001935
Adam Cohen228da5a2011-07-27 22:23:47 -07001936 if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values) < 0) {
1937 return -1;
1938 }
1939 return id;
Mike Cleronb87bd162009-10-30 16:36:56 -07001940 }
Dan Sandlerd5024042014-01-09 15:01:33 -05001941
Sunny Goyal0fe505b2014-08-06 09:55:36 -07001942 private void migrateLauncher2Shortcuts(SQLiteDatabase db, Uri uri) {
Dan Sandlerd5024042014-01-09 15:01:33 -05001943 final ContentResolver resolver = mContext.getContentResolver();
1944 Cursor c = null;
1945 int count = 0;
1946 int curScreen = 0;
1947
1948 try {
1949 c = resolver.query(uri, null, null, null, "title ASC");
1950 } catch (Exception e) {
1951 // Ignore
1952 }
1953
Dan Sandlerd5024042014-01-09 15:01:33 -05001954 // We already have a favorites database in the old provider
1955 if (c != null) {
1956 try {
1957 if (c.getCount() > 0) {
1958 final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
1959 final int intentIndex
1960 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.INTENT);
1961 final int titleIndex
1962 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE);
1963 final int iconTypeIndex
1964 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_TYPE);
1965 final int iconIndex
1966 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON);
1967 final int iconPackageIndex
1968 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_PACKAGE);
1969 final int iconResourceIndex
1970 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_RESOURCE);
1971 final int containerIndex
1972 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER);
1973 final int itemTypeIndex
1974 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE);
1975 final int screenIndex
1976 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN);
1977 final int cellXIndex
1978 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX);
1979 final int cellYIndex
1980 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY);
1981 final int uriIndex
1982 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.URI);
1983 final int displayModeIndex
1984 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.DISPLAY_MODE);
Kenny Guyed131872014-04-30 03:02:21 +01001985 final int profileIndex
1986 = c.getColumnIndex(LauncherSettings.Favorites.PROFILE_ID);
Dan Sandlerd5024042014-01-09 15:01:33 -05001987
1988 int i = 0;
1989 int curX = 0;
1990 int curY = 0;
1991
1992 final LauncherAppState app = LauncherAppState.getInstance();
1993 final DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
1994 final int width = (int) grid.numColumns;
1995 final int height = (int) grid.numRows;
1996 final int hotseatWidth = (int) grid.numHotseatIcons;
1997
1998 final HashSet<String> seenIntents = new HashSet<String>(c.getCount());
1999
Adam Cohen72960972014-01-15 18:13:55 -08002000 final ArrayList<ContentValues> shortcuts = new ArrayList<ContentValues>();
2001 final ArrayList<ContentValues> folders = new ArrayList<ContentValues>();
Dan Sandlerab5fa3a2014-03-06 23:48:04 -05002002 final SparseArray<ContentValues> hotseat = new SparseArray<ContentValues>();
Dan Sandlerd5024042014-01-09 15:01:33 -05002003
2004 while (c.moveToNext()) {
2005 final int itemType = c.getInt(itemTypeIndex);
2006 if (itemType != Favorites.ITEM_TYPE_APPLICATION
2007 && itemType != Favorites.ITEM_TYPE_SHORTCUT
2008 && itemType != Favorites.ITEM_TYPE_FOLDER) {
2009 continue;
2010 }
2011
2012 final int cellX = c.getInt(cellXIndex);
2013 final int cellY = c.getInt(cellYIndex);
2014 final int screen = c.getInt(screenIndex);
2015 int container = c.getInt(containerIndex);
2016 final String intentStr = c.getString(intentIndex);
Kenny Guyed131872014-04-30 03:02:21 +01002017
2018 UserManagerCompat userManager = UserManagerCompat.getInstance(mContext);
2019 UserHandleCompat userHandle;
2020 final long userSerialNumber;
2021 if (profileIndex != -1 && !c.isNull(profileIndex)) {
2022 userSerialNumber = c.getInt(profileIndex);
2023 userHandle = userManager.getUserForSerialNumber(userSerialNumber);
2024 } else {
2025 // Default to the serial number of this user, for older
2026 // shortcuts.
2027 userHandle = UserHandleCompat.myUserHandle();
2028 userSerialNumber = userManager.getSerialNumberForUser(userHandle);
2029 }
Dan Sandlerd5024042014-01-09 15:01:33 -05002030 Launcher.addDumpLog(TAG, "migrating \""
Dan Sandlerab5fa3a2014-03-06 23:48:04 -05002031 + c.getString(titleIndex) + "\" ("
2032 + cellX + "," + cellY + "@"
2033 + LauncherSettings.Favorites.containerToString(container)
2034 + "/" + screen
2035 + "): " + intentStr, true);
Dan Sandlerd5024042014-01-09 15:01:33 -05002036
2037 if (itemType != Favorites.ITEM_TYPE_FOLDER) {
Adam Cohen556f6132014-01-15 15:18:08 -08002038
2039 final Intent intent;
2040 final ComponentName cn;
2041 try {
2042 intent = Intent.parseUri(intentStr, 0);
2043 } catch (URISyntaxException e) {
2044 // bogus intent?
2045 Launcher.addDumpLog(TAG,
2046 "skipping invalid intent uri", true);
2047 continue;
2048 }
2049
2050 cn = intent.getComponent();
Dan Sandlerd5024042014-01-09 15:01:33 -05002051 if (TextUtils.isEmpty(intentStr)) {
2052 // no intent? no icon
2053 Launcher.addDumpLog(TAG, "skipping empty intent", true);
2054 continue;
Adam Cohen72960972014-01-15 18:13:55 -08002055 } else if (cn != null &&
Kenny Guyed131872014-04-30 03:02:21 +01002056 !LauncherModel.isValidPackageActivity(mContext, cn,
2057 userHandle)) {
Adam Cohen556f6132014-01-15 15:18:08 -08002058 // component no longer exists.
Adam Cohen72960972014-01-15 18:13:55 -08002059 Launcher.addDumpLog(TAG, "skipping item whose component " +
Adam Cohen556f6132014-01-15 15:18:08 -08002060 "no longer exists.", true);
2061 continue;
Adam Cohen72960972014-01-15 18:13:55 -08002062 } else if (container ==
2063 LauncherSettings.Favorites.CONTAINER_DESKTOP) {
2064 // Dedupe icons directly on the workspace
2065
Adam Cohen556f6132014-01-15 15:18:08 -08002066 // Canonicalize
2067 // the Play Store sets the package parameter, but Launcher
2068 // does not, so we clear that out to keep them the same
2069 intent.setPackage(null);
2070 final String key = intent.toUri(0);
2071 if (seenIntents.contains(key)) {
2072 Launcher.addDumpLog(TAG, "skipping duplicate", true);
Dan Sandlerd5024042014-01-09 15:01:33 -05002073 continue;
Adam Cohen556f6132014-01-15 15:18:08 -08002074 } else {
2075 seenIntents.add(key);
Dan Sandlerd5024042014-01-09 15:01:33 -05002076 }
2077 }
2078 }
2079
2080 ContentValues values = new ContentValues(c.getColumnCount());
2081 values.put(LauncherSettings.Favorites._ID, c.getInt(idIndex));
2082 values.put(LauncherSettings.Favorites.INTENT, intentStr);
2083 values.put(LauncherSettings.Favorites.TITLE, c.getString(titleIndex));
2084 values.put(LauncherSettings.Favorites.ICON_TYPE,
2085 c.getInt(iconTypeIndex));
2086 values.put(LauncherSettings.Favorites.ICON, c.getBlob(iconIndex));
2087 values.put(LauncherSettings.Favorites.ICON_PACKAGE,
2088 c.getString(iconPackageIndex));
2089 values.put(LauncherSettings.Favorites.ICON_RESOURCE,
2090 c.getString(iconResourceIndex));
2091 values.put(LauncherSettings.Favorites.ITEM_TYPE, itemType);
2092 values.put(LauncherSettings.Favorites.APPWIDGET_ID, -1);
2093 values.put(LauncherSettings.Favorites.URI, c.getString(uriIndex));
2094 values.put(LauncherSettings.Favorites.DISPLAY_MODE,
2095 c.getInt(displayModeIndex));
Kenny Guyed131872014-04-30 03:02:21 +01002096 values.put(LauncherSettings.Favorites.PROFILE_ID, userSerialNumber);
Dan Sandlerd5024042014-01-09 15:01:33 -05002097
Dan Sandlerab5fa3a2014-03-06 23:48:04 -05002098 if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
2099 hotseat.put(screen, values);
Dan Sandlerd5024042014-01-09 15:01:33 -05002100 }
2101
2102 if (container != LauncherSettings.Favorites.CONTAINER_DESKTOP) {
2103 // In a folder or in the hotseat, preserve position
2104 values.put(LauncherSettings.Favorites.SCREEN, screen);
2105 values.put(LauncherSettings.Favorites.CELLX, cellX);
2106 values.put(LauncherSettings.Favorites.CELLY, cellY);
2107 } else {
Adam Cohen72960972014-01-15 18:13:55 -08002108 // For items contained directly on one of the workspace screen,
2109 // we'll determine their location (screen, x, y) in a second pass.
Dan Sandlerd5024042014-01-09 15:01:33 -05002110 }
2111
2112 values.put(LauncherSettings.Favorites.CONTAINER, container);
2113
Adam Cohen72960972014-01-15 18:13:55 -08002114 if (itemType != Favorites.ITEM_TYPE_FOLDER) {
2115 shortcuts.add(values);
2116 } else {
2117 folders.add(values);
2118 }
Dan Sandlerd5024042014-01-09 15:01:33 -05002119 }
2120
Dan Sandlerab5fa3a2014-03-06 23:48:04 -05002121 // Now that we have all the hotseat icons, let's go through them left-right
2122 // and assign valid locations for them in the new hotseat
2123 final int N = hotseat.size();
2124 for (int idx=0; idx<N; idx++) {
2125 int hotseatX = hotseat.keyAt(idx);
2126 ContentValues values = hotseat.valueAt(idx);
2127
2128 if (hotseatX == grid.hotseatAllAppsRank) {
2129 // let's drop this in the next available hole in the hotseat
2130 while (++hotseatX < hotseatWidth) {
2131 if (hotseat.get(hotseatX) == null) {
2132 // found a spot! move it here
2133 values.put(LauncherSettings.Favorites.SCREEN,
2134 hotseatX);
2135 break;
2136 }
2137 }
2138 }
2139 if (hotseatX >= hotseatWidth) {
2140 // no room for you in the hotseat? it's off to the desktop with you
2141 values.put(LauncherSettings.Favorites.CONTAINER,
2142 Favorites.CONTAINER_DESKTOP);
2143 }
2144 }
2145
Adam Cohen72960972014-01-15 18:13:55 -08002146 final ArrayList<ContentValues> allItems = new ArrayList<ContentValues>();
2147 // Folders first
2148 allItems.addAll(folders);
2149 // Then shortcuts
2150 allItems.addAll(shortcuts);
2151
2152 // Layout all the folders
2153 for (ContentValues values: allItems) {
2154 if (values.getAsInteger(LauncherSettings.Favorites.CONTAINER) !=
2155 LauncherSettings.Favorites.CONTAINER_DESKTOP) {
2156 // Hotseat items and folder items have already had their
2157 // location information set. Nothing to be done here.
2158 continue;
2159 }
2160 values.put(LauncherSettings.Favorites.SCREEN, curScreen);
2161 values.put(LauncherSettings.Favorites.CELLX, curX);
2162 values.put(LauncherSettings.Favorites.CELLY, curY);
2163 curX = (curX + 1) % width;
2164 if (curX == 0) {
2165 curY = (curY + 1);
2166 }
2167 // Leave the last row of icons blank on every screen
2168 if (curY == height - 1) {
2169 curScreen = (int) generateNewScreenId();
2170 curY = 0;
2171 }
2172 }
2173
2174 if (allItems.size() > 0) {
Dan Sandlerd5024042014-01-09 15:01:33 -05002175 db.beginTransaction();
2176 try {
Adam Cohen72960972014-01-15 18:13:55 -08002177 for (ContentValues row: allItems) {
2178 if (row == null) continue;
2179 if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, row)
Dan Sandlerd5024042014-01-09 15:01:33 -05002180 < 0) {
2181 return;
2182 } else {
2183 count++;
2184 }
2185 }
2186 db.setTransactionSuccessful();
2187 } finally {
2188 db.endTransaction();
2189 }
2190 }
2191
2192 db.beginTransaction();
2193 try {
2194 for (i=0; i<=curScreen; i++) {
2195 final ContentValues values = new ContentValues();
2196 values.put(LauncherSettings.WorkspaceScreens._ID, i);
2197 values.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, i);
2198 if (dbInsertAndCheck(this, db, TABLE_WORKSPACE_SCREENS, null, values)
2199 < 0) {
2200 return;
2201 }
2202 }
2203 db.setTransactionSuccessful();
2204 } finally {
2205 db.endTransaction();
2206 }
2207 }
2208 } finally {
2209 c.close();
2210 }
2211 }
2212
2213 Launcher.addDumpLog(TAG, "migrated " + count + " icons from Launcher2 into "
2214 + (curScreen+1) + " screens", true);
2215
2216 // ensure that new screens are created to hold these icons
2217 setFlagJustLoadedOldDb();
2218
2219 // Update max IDs; very important since we just grabbed IDs from another database
2220 mMaxItemId = initializeMaxItemId(db);
2221 mMaxScreenId = initializeMaxScreenId(db);
2222 if (LOGD) Log.d(TAG, "mMaxItemId: " + mMaxItemId + " mMaxScreenId: " + mMaxScreenId);
2223 }
Mike Cleronb87bd162009-10-30 16:36:56 -07002224 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -07002225
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002226 /**
2227 * Build a query string that will match any row where the column matches
2228 * anything in the values list.
2229 */
Sunny Goyal0fe505b2014-08-06 09:55:36 -07002230 private static String buildOrWhereString(String column, int[] values) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002231 StringBuilder selectWhere = new StringBuilder();
2232 for (int i = values.length - 1; i >= 0; i--) {
2233 selectWhere.append(column).append("=").append(values[i]);
2234 if (i > 0) {
2235 selectWhere.append(" OR ");
2236 }
2237 }
2238 return selectWhere.toString();
2239 }
2240
Jeff Sharkey5aeef582014-04-14 13:26:42 -07002241 /**
2242 * Return attribute value, attempting launcher-specific namespace first
2243 * before falling back to anonymous attribute.
2244 */
Sunny Goyal0fe505b2014-08-06 09:55:36 -07002245 private static String getAttributeValue(XmlResourceParser parser, String attribute) {
Jeff Sharkey5aeef582014-04-14 13:26:42 -07002246 String value = parser.getAttributeValue(
2247 "http://schemas.android.com/apk/res-auto/com.android.launcher3", attribute);
2248 if (value == null) {
2249 value = parser.getAttributeValue(null, attribute);
2250 }
2251 return value;
2252 }
2253
2254 /**
2255 * Return attribute resource value, attempting launcher-specific namespace
2256 * first before falling back to anonymous attribute.
2257 */
Sunny Goyal0fe505b2014-08-06 09:55:36 -07002258 private static int getAttributeResourceValue(XmlResourceParser parser, String attribute,
Jeff Sharkey5aeef582014-04-14 13:26:42 -07002259 int defaultValue) {
2260 int value = parser.getAttributeResourceValue(
2261 "http://schemas.android.com/apk/res-auto/com.android.launcher3", attribute,
2262 defaultValue);
2263 if (value == defaultValue) {
2264 value = parser.getAttributeResourceValue(null, attribute, defaultValue);
2265 }
2266 return value;
2267 }
2268
2269 private static void copyInteger(ContentValues from, ContentValues to, String key) {
2270 to.put(key, from.getAsInteger(key));
2271 }
2272
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002273 static class SqlArguments {
2274 public final String table;
2275 public final String where;
2276 public final String[] args;
2277
2278 SqlArguments(Uri url, String where, String[] args) {
2279 if (url.getPathSegments().size() == 1) {
2280 this.table = url.getPathSegments().get(0);
2281 this.where = where;
2282 this.args = args;
2283 } else if (url.getPathSegments().size() != 2) {
2284 throw new IllegalArgumentException("Invalid URI: " + url);
2285 } else if (!TextUtils.isEmpty(where)) {
2286 throw new UnsupportedOperationException("WHERE clause not supported: " + url);
2287 } else {
2288 this.table = url.getPathSegments().get(0);
Daniel Lehmannc3a80402012-04-23 21:35:11 -07002289 this.where = "_id=" + ContentUris.parseId(url);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002290 this.args = null;
2291 }
2292 }
2293
2294 SqlArguments(Uri url) {
2295 if (url.getPathSegments().size() == 1) {
2296 table = url.getPathSegments().get(0);
2297 where = null;
2298 args = null;
2299 } else {
2300 throw new IllegalArgumentException("Invalid URI: " + url);
2301 }
2302 }
2303 }
Sunny Goyal0fe505b2014-08-06 09:55:36 -07002304
2305 static interface WorkspaceLoader {
2306 /**
2307 * @param screenIds A mutable list of screen its
2308 * @return the number of workspace items added.
2309 */
2310 int loadLayout(SQLiteDatabase db, ArrayList<Long> screenIds);
2311 }
2312
2313 private static class SimpleWorkspaceLoader implements WorkspaceLoader {
2314 private final Resources mRes;
2315 private final int mWorkspaceId;
2316 private final DatabaseHelper mHelper;
2317
2318 SimpleWorkspaceLoader(DatabaseHelper helper, Resources res, int workspaceId) {
2319 mHelper = helper;
2320 mRes = res;
2321 mWorkspaceId = workspaceId;
2322 }
2323
2324 @Override
2325 public int loadLayout(SQLiteDatabase db, ArrayList<Long> screenIds) {
2326 return mHelper.loadFavoritesRecursive(db, mRes, mWorkspaceId, screenIds);
2327 }
2328 }
Adam Cohen72960972014-01-15 18:13:55 -08002329}