blob: 6cc1688de5471bb02788a82d34935034fb871c33 [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);
Adam Coheneb1ac422014-10-14 08:55:28 -07001293 if (!appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId,cn)) {
Adam Cohend61a6382014-10-14 15:04:34 -07001294 return false;
Adam Cohena043fa82014-07-23 14:49:38 -07001295 }
1296 } catch (RuntimeException e) {
1297 Log.e(TAG, "Failed to initialize external widget", e);
Adam Coheneb1ac422014-10-14 08:55:28 -07001298 return false;
Adam Cohena043fa82014-07-23 14:49:38 -07001299 }
Adam Coheneb1ac422014-10-14 08:55:28 -07001300 } else {
1301 return false;
Adam Cohena043fa82014-07-23 14:49:38 -07001302 }
1303 }
Adam Cohen7ec3bbf2014-07-31 00:09:45 -07001304
1305 // Add screen id if not present
1306 long screenId = values.getAsLong(LauncherSettings.Favorites.SCREEN);
1307 if (!addScreenIdIfNecessary(screenId)) {
1308 return false;
1309 }
Adam Cohena043fa82014-07-23 14:49:38 -07001310 return true;
1311 }
1312
Adam Cohen7ec3bbf2014-07-31 00:09:45 -07001313 // Returns true of screen id exists, or if successfully added
1314 private boolean addScreenIdIfNecessary(long screenId) {
1315 if (!hasScreenId(screenId)) {
1316 int rank = getMaxScreenRank() + 1;
1317
1318 ContentValues v = new ContentValues();
1319 v.put(LauncherSettings.WorkspaceScreens._ID, screenId);
1320 v.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, rank);
1321 if (dbInsertAndCheck(this, getWritableDatabase(),
1322 TABLE_WORKSPACE_SCREENS, null, v) < 0) {
1323 return false;
1324 }
1325 }
1326 return true;
1327 }
1328
1329 private boolean hasScreenId(long screenId) {
1330 SQLiteDatabase db = getWritableDatabase();
1331 Cursor c = db.rawQuery("SELECT * FROM " + TABLE_WORKSPACE_SCREENS + " WHERE "
1332 + LauncherSettings.WorkspaceScreens._ID + " = " + screenId, null);
1333 if (c != null) {
1334 int count = c.getCount();
1335 c.close();
1336 return count > 0;
1337 } else {
1338 return false;
1339 }
1340 }
1341
1342 private int getMaxScreenRank() {
1343 SQLiteDatabase db = getWritableDatabase();
1344 Cursor c = db.rawQuery("SELECT MAX(" + LauncherSettings.WorkspaceScreens.SCREEN_RANK
1345 + ") FROM " + TABLE_WORKSPACE_SCREENS, null);
1346
1347 // get the result
1348 final int maxRankIndex = 0;
1349 int rank = -1;
1350 if (c != null && c.moveToNext()) {
1351 rank = c.getInt(maxRankIndex);
1352 }
1353 if (c != null) {
1354 c.close();
1355 }
1356
1357 return rank;
1358 }
1359
Michael Jurka8b805b12012-04-18 14:23:14 -07001360 private static final void beginDocument(XmlPullParser parser, String firstElementName)
1361 throws XmlPullParserException, IOException {
1362 int type;
Michael Jurka9bc8eba2012-05-21 20:36:44 -07001363 while ((type = parser.next()) != XmlPullParser.START_TAG
1364 && type != XmlPullParser.END_DOCUMENT) {
Michael Jurka8b805b12012-04-18 14:23:14 -07001365 ;
1366 }
1367
Michael Jurka9bc8eba2012-05-21 20:36:44 -07001368 if (type != XmlPullParser.START_TAG) {
Michael Jurka8b805b12012-04-18 14:23:14 -07001369 throw new XmlPullParserException("No start tag found");
1370 }
1371
1372 if (!parser.getName().equals(firstElementName)) {
1373 throw new XmlPullParserException("Unexpected start tag: found " + parser.getName() +
1374 ", expected " + firstElementName);
1375 }
1376 }
1377
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001378 private static Intent buildMainIntent() {
1379 Intent intent = new Intent(Intent.ACTION_MAIN, null);
1380 intent.addCategory(Intent.CATEGORY_LAUNCHER);
1381 return intent;
1382 }
1383
Sunny Goyal0fe505b2014-08-06 09:55:36 -07001384 private int loadFavorites(SQLiteDatabase db, WorkspaceLoader loader) {
Adam Cohen71483f42014-05-15 14:04:01 -07001385 ArrayList<Long> screenIds = new ArrayList<Long>();
Sunny Goyal0fe505b2014-08-06 09:55:36 -07001386 // TODO: Use multiple loaders with fall-back and transaction.
1387 int count = loader.loadLayout(db, screenIds);
Adam Cohen71483f42014-05-15 14:04:01 -07001388
1389 // Add the screens specified by the items above
1390 Collections.sort(screenIds);
1391 int rank = 0;
1392 ContentValues values = new ContentValues();
1393 for (Long id : screenIds) {
1394 values.clear();
1395 values.put(LauncherSettings.WorkspaceScreens._ID, id);
1396 values.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, rank);
1397 if (dbInsertAndCheck(this, db, TABLE_WORKSPACE_SCREENS, null, values) < 0) {
1398 throw new RuntimeException("Failed initialize screen table"
1399 + "from default layout");
1400 }
1401 rank++;
1402 }
1403
1404 // Ensure that the max ids are initialized
1405 mMaxItemId = initializeMaxItemId(db);
1406 mMaxScreenId = initializeMaxScreenId(db);
Adam Cohen7ec3bbf2014-07-31 00:09:45 -07001407
Adam Cohen71483f42014-05-15 14:04:01 -07001408 return count;
1409 }
1410
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001411 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001412 * Loads the default set of favorite packages from an xml file.
1413 *
1414 * @param db The database to write the values into
Winson Chung3d503fb2011-07-13 17:25:49 -07001415 * @param filterContainerId The specific container id of items to load
Adam Cohen71483f42014-05-15 14:04:01 -07001416 * @param the set of screenIds which are used by the favorites
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001417 */
Adam Cohen9b8f51f2014-05-30 15:34:09 -07001418 private int loadFavoritesRecursive(SQLiteDatabase db, Resources res, int workspaceResourceId,
Adam Cohen71483f42014-05-15 14:04:01 -07001419 ArrayList<Long> screenIds) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001420
Adam Cohen71483f42014-05-15 14:04:01 -07001421 ContentValues values = new ContentValues();
Daniel Sandler57dac262013-10-03 13:28:36 -04001422 if (LOGD) Log.v(TAG, String.format("Loading favorites from resid=0x%08x", workspaceResourceId));
1423
Adam Cohen71483f42014-05-15 14:04:01 -07001424 int count = 0;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001425 try {
Adam Cohen9b8f51f2014-05-30 15:34:09 -07001426 XmlResourceParser parser = res.getXml(workspaceResourceId);
Michael Jurka8b805b12012-04-18 14:23:14 -07001427 beginDocument(parser, TAG_FAVORITES);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001428
The Android Open Source Projectf96811c2009-03-18 17:39:48 -07001429 final int depth = parser.getDepth();
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001430
The Android Open Source Projectf96811c2009-03-18 17:39:48 -07001431 int type;
1432 while (((type = parser.next()) != XmlPullParser.END_TAG ||
1433 parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
1434
1435 if (type != XmlPullParser.START_TAG) {
1436 continue;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001437 }
1438
The Android Open Source Projectf96811c2009-03-18 17:39:48 -07001439 boolean added = false;
1440 final String name = parser.getName();
1441
Daniel Sandler57dac262013-10-03 13:28:36 -04001442 if (TAG_INCLUDE.equals(name)) {
Daniel Sandler57dac262013-10-03 13:28:36 -04001443
Adam Cohen9b8f51f2014-05-30 15:34:09 -07001444 final int resId = getAttributeResourceValue(parser, ATTR_WORKSPACE, 0);
Daniel Sandler57dac262013-10-03 13:28:36 -04001445
1446 if (LOGD) Log.v(TAG, String.format(("%" + (2*(depth+1)) + "s<include workspace=%08x>"),
1447 "", resId));
1448
1449 if (resId != 0 && resId != workspaceResourceId) {
1450 // recursively load some more favorites, why not?
Adam Cohen9b8f51f2014-05-30 15:34:09 -07001451 count += loadFavoritesRecursive(db, res, resId, screenIds);
Daniel Sandler57dac262013-10-03 13:28:36 -04001452 added = false;
Daniel Sandler57dac262013-10-03 13:28:36 -04001453 } else {
1454 Log.w(TAG, String.format("Skipping <include workspace=0x%08x>", resId));
1455 }
1456
Daniel Sandler57dac262013-10-03 13:28:36 -04001457 if (LOGD) Log.v(TAG, String.format(("%" + (2*(depth+1)) + "s</include>"), ""));
1458 continue;
1459 }
1460
1461 // Assuming it's a <favorite> at this point
Winson Chung3d503fb2011-07-13 17:25:49 -07001462 long container = LauncherSettings.Favorites.CONTAINER_DESKTOP;
Adam Cohen9b8f51f2014-05-30 15:34:09 -07001463 String strContainer = getAttributeValue(parser, ATTR_CONTAINER);
1464 if (strContainer != null) {
1465 container = Long.valueOf(strContainer);
Winson Chung3d503fb2011-07-13 17:25:49 -07001466 }
The Android Open Source Projectf96811c2009-03-18 17:39:48 -07001467
Adam Cohen9b8f51f2014-05-30 15:34:09 -07001468 String screen = getAttributeValue(parser, ATTR_SCREEN);
1469 String x = getAttributeValue(parser, ATTR_X);
1470 String y = getAttributeValue(parser, ATTR_Y);
Winson Chung6d092682011-11-16 18:43:26 -08001471
Winson Chung6d092682011-11-16 18:43:26 -08001472 values.clear();
1473 values.put(LauncherSettings.Favorites.CONTAINER, container);
1474 values.put(LauncherSettings.Favorites.SCREEN, screen);
1475 values.put(LauncherSettings.Favorites.CELLX, x);
1476 values.put(LauncherSettings.Favorites.CELLY, y);
1477
Daniel Sandler57dac262013-10-03 13:28:36 -04001478 if (LOGD) {
Adam Cohen9b8f51f2014-05-30 15:34:09 -07001479 final String title = getAttributeValue(parser, ATTR_TITLE);
1480 final String pkg = getAttributeValue(parser, ATTR_PACKAGE_NAME);
Daniel Sandler57dac262013-10-03 13:28:36 -04001481 final String something = title != null ? title : pkg;
1482 Log.v(TAG, String.format(
1483 ("%" + (2*(depth+1)) + "s<%s%s c=%d s=%s x=%s y=%s>"),
1484 "", name,
1485 (something == null ? "" : (" \"" + something + "\"")),
1486 container, screen, x, y));
1487 }
1488
Winson Chung6d092682011-11-16 18:43:26 -08001489 if (TAG_FAVORITE.equals(name)) {
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001490 long id = addAppShortcut(db, values, parser);
Winson Chung6d092682011-11-16 18:43:26 -08001491 added = id >= 0;
Winson Chung6d092682011-11-16 18:43:26 -08001492 } else if (TAG_APPWIDGET.equals(name)) {
Adam Cohen9b8f51f2014-05-30 15:34:09 -07001493 added = addAppWidget(parser, type, db, values);
Winson Chung6d092682011-11-16 18:43:26 -08001494 } else if (TAG_SHORTCUT.equals(name)) {
Adam Cohen9b8f51f2014-05-30 15:34:09 -07001495 long id = addUriShortcut(db, values, res, parser);
Winson Chung6d092682011-11-16 18:43:26 -08001496 added = id >= 0;
Jason Monk41314972014-03-03 16:11:30 -05001497 } else if (TAG_RESOLVE.equals(name)) {
1498 // This looks through the contained favorites (or meta-favorites) and
1499 // attempts to add them as shortcuts in the fallback group's location
1500 // until one is added successfully.
1501 added = false;
1502 final int groupDepth = parser.getDepth();
1503 while ((type = parser.next()) != XmlPullParser.END_TAG ||
1504 parser.getDepth() > groupDepth) {
1505 if (type != XmlPullParser.START_TAG) {
1506 continue;
1507 }
1508 final String fallback_item_name = parser.getName();
Jason Monk41314972014-03-03 16:11:30 -05001509 if (!added) {
1510 if (TAG_FAVORITE.equals(fallback_item_name)) {
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001511 final long id = addAppShortcut(db, values, parser);
Jason Monk41314972014-03-03 16:11:30 -05001512 added = id >= 0;
1513 } else {
Adam Cohen9b8f51f2014-05-30 15:34:09 -07001514 Log.e(TAG, "Fallback groups can contain only favorites, found "
1515 + fallback_item_name);
Jason Monk41314972014-03-03 16:11:30 -05001516 }
1517 }
Jason Monk41314972014-03-03 16:11:30 -05001518 }
Winson Chung6d092682011-11-16 18:43:26 -08001519 } else if (TAG_FOLDER.equals(name)) {
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001520 // Folder contents are nested in this XML file
Adam Cohen43f3ca02014-07-25 13:24:09 -07001521 added = loadFolder(db, values, res, parser);
Winson Chung3d503fb2011-07-13 17:25:49 -07001522
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001523 } else if (TAG_PARTNER_FOLDER.equals(name)) {
1524 // Folder contents come from an external XML resource
1525 final Partner partner = Partner.get(mPackageManager);
1526 if (partner != null) {
1527 final Resources partnerRes = partner.getResources();
Adam Cohen4ae96ce2014-08-29 15:05:48 -07001528 final int resId = partnerRes.getIdentifier(Partner.RES_FOLDER,
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001529 "xml", partner.getPackageName());
1530 if (resId != 0) {
1531 final XmlResourceParser partnerParser = partnerRes.getXml(resId);
1532 beginDocument(partnerParser, TAG_FOLDER);
1533 added = loadFolder(db, values, partnerRes, partnerParser);
Winson Chung6d092682011-11-16 18:43:26 -08001534 }
Winson Chung3d503fb2011-07-13 17:25:49 -07001535 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001536 }
Adam Cohen71483f42014-05-15 14:04:01 -07001537 if (added) {
1538 long screenId = Long.parseLong(screen);
1539 // Keep track of the set of screens which need to be added to the db.
1540 if (!screenIds.contains(screenId) &&
1541 container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
1542 screenIds.add(screenId);
1543 }
1544 count++;
1545 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001546 }
1547 } catch (XmlPullParserException e) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -08001548 Log.w(TAG, "Got exception parsing favorites.", e);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001549 } catch (IOException e) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -08001550 Log.w(TAG, "Got exception parsing favorites.", e);
Winson Chung3d503fb2011-07-13 17:25:49 -07001551 } catch (RuntimeException e) {
1552 Log.w(TAG, "Got exception parsing favorites.", e);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001553 }
Adam Cohen71483f42014-05-15 14:04:01 -07001554 return count;
The Android Open Source Projectf96811c2009-03-18 17:39:48 -07001555 }
1556
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001557 /**
Adam Coheneb1ac422014-10-14 08:55:28 -07001558 * Parse folder items starting at {@link XmlPullParser} location. Allow recursive
1559 * includes of items.
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001560 */
Adam Coheneb1ac422014-10-14 08:55:28 -07001561 private void addToFolder(SQLiteDatabase db, Resources res, XmlResourceParser parser,
1562 ArrayList<Long> folderItems, long folderId) throws IOException, XmlPullParserException {
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001563 int type;
1564 int folderDepth = parser.getDepth();
1565 while ((type = parser.next()) != XmlPullParser.END_TAG ||
1566 parser.getDepth() > folderDepth) {
1567 if (type != XmlPullParser.START_TAG) {
1568 continue;
1569 }
1570 final String tag = parser.getName();
1571
1572 final ContentValues childValues = new ContentValues();
1573 childValues.put(LauncherSettings.Favorites.CONTAINER, folderId);
1574
1575 if (LOGD) {
1576 final String pkg = getAttributeValue(parser, ATTR_PACKAGE_NAME);
1577 final String uri = getAttributeValue(parser, ATTR_URI);
1578 Log.v(TAG, String.format(("%" + (2*(folderDepth+1)) + "s<%s \"%s\">"), "",
1579 tag, uri != null ? uri : pkg));
1580 }
1581
1582 if (TAG_FAVORITE.equals(tag) && folderId >= 0) {
1583 final long id = addAppShortcut(db, childValues, parser);
1584 if (id >= 0) {
1585 folderItems.add(id);
1586 }
1587 } else if (TAG_SHORTCUT.equals(tag) && folderId >= 0) {
1588 final long id = addUriShortcut(db, childValues, res, parser);
1589 if (id >= 0) {
1590 folderItems.add(id);
1591 }
Adam Coheneb1ac422014-10-14 08:55:28 -07001592 } else if (TAG_INCLUDE.equals(tag) && folderId >= 0) {
1593 addToFolder(db, res, parser, folderItems, folderId);
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001594 } else {
1595 throw new RuntimeException("Folders can contain only shortcuts");
1596 }
1597 }
Adam Coheneb1ac422014-10-14 08:55:28 -07001598 }
1599
1600 /**
1601 * Parse folder starting at current {@link XmlPullParser} location.
1602 */
1603 private boolean loadFolder(SQLiteDatabase db, ContentValues values, Resources res,
1604 XmlResourceParser parser) throws IOException, XmlPullParserException {
1605 final String title;
1606 final int titleResId = getAttributeResourceValue(parser, ATTR_TITLE, 0);
1607 if (titleResId != 0) {
1608 title = res.getString(titleResId);
1609 } else {
1610 title = mContext.getResources().getString(R.string.folder_name);
1611 }
1612
1613 values.put(LauncherSettings.Favorites.TITLE, title);
1614 long folderId = addFolder(db, values);
1615 boolean added = folderId >= 0;
1616
1617 ArrayList<Long> folderItems = new ArrayList<Long>();
1618 addToFolder(db, res, parser, folderItems, folderId);
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001619
1620 // We can only have folders with >= 2 items, so we need to remove the
1621 // folder and clean up if less than 2 items were included, or some
1622 // failed to add, and less than 2 were actually added
1623 if (folderItems.size() < 2 && folderId >= 0) {
1624 // Delete the folder
1625 deleteId(db, folderId);
1626
1627 // If we have a single item, promote it to where the folder
1628 // would have been.
1629 if (folderItems.size() == 1) {
1630 final ContentValues childValues = new ContentValues();
1631 copyInteger(values, childValues, LauncherSettings.Favorites.CONTAINER);
1632 copyInteger(values, childValues, LauncherSettings.Favorites.SCREEN);
1633 copyInteger(values, childValues, LauncherSettings.Favorites.CELLX);
1634 copyInteger(values, childValues, LauncherSettings.Favorites.CELLY);
1635
1636 final long id = folderItems.get(0);
1637 db.update(TABLE_FAVORITES, childValues,
1638 LauncherSettings.Favorites._ID + "=" + id, null);
1639 } else {
1640 added = false;
1641 }
1642 }
1643 return added;
1644 }
1645
Jason Monk41314972014-03-03 16:11:30 -05001646 // A meta shortcut attempts to resolve an intent specified as a URI in the XML, if a
1647 // logical choice for what shortcut should be used for that intent exists, then it is
1648 // added. Otherwise add nothing.
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001649 private long addAppShortcutByUri(SQLiteDatabase db, ContentValues values,
1650 String intentUri) {
Jason Monk41314972014-03-03 16:11:30 -05001651 Intent metaIntent;
The Android Open Source Projectf96811c2009-03-18 17:39:48 -07001652 try {
Jason Monk41314972014-03-03 16:11:30 -05001653 metaIntent = Intent.parseUri(intentUri, 0);
1654 } catch (URISyntaxException e) {
1655 Log.e(TAG, "Unable to add meta-favorite: " + intentUri, e);
1656 return -1;
1657 }
1658
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001659 ResolveInfo resolved = mPackageManager.resolveActivity(metaIntent,
Jason Monk41314972014-03-03 16:11:30 -05001660 PackageManager.MATCH_DEFAULT_ONLY);
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001661 final List<ResolveInfo> appList = mPackageManager.queryIntentActivities(
Jason Monk41314972014-03-03 16:11:30 -05001662 metaIntent, PackageManager.MATCH_DEFAULT_ONLY);
1663
1664 // Verify that the result is an app and not just the resolver dialog asking which
1665 // app to use.
1666 if (wouldLaunchResolverActivity(resolved, appList)) {
1667 // If only one of the results is a system app then choose that as the default.
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001668 final ResolveInfo systemApp = getSingleSystemActivity(appList);
Jason Monk41314972014-03-03 16:11:30 -05001669 if (systemApp == null) {
1670 // There is no logical choice for this meta-favorite, so rather than making
1671 // a bad choice just add nothing.
1672 Log.w(TAG, "No preference or single system activity found for "
1673 + metaIntent.toString());
Adam Cohen228da5a2011-07-27 22:23:47 -07001674 return -1;
1675 }
Jason Monk41314972014-03-03 16:11:30 -05001676 resolved = systemApp;
The Android Open Source Projectf96811c2009-03-18 17:39:48 -07001677 }
Jason Monk41314972014-03-03 16:11:30 -05001678 final ActivityInfo info = resolved.activityInfo;
Jason Monkc3009c02014-08-20 09:41:21 -04001679 final Intent intent = mPackageManager.getLaunchIntentForPackage(info.packageName);
1680 if (intent == null) {
1681 return -1;
1682 }
Jason Monk41314972014-03-03 16:11:30 -05001683 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
1684 Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
1685
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001686 return addAppShortcut(db, values, info.loadLabel(mPackageManager).toString(), intent);
Jason Monk41314972014-03-03 16:11:30 -05001687 }
1688
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001689 private ResolveInfo getSingleSystemActivity(List<ResolveInfo> appList) {
Jason Monk41314972014-03-03 16:11:30 -05001690 ResolveInfo systemResolve = null;
1691 final int N = appList.size();
1692 for (int i = 0; i < N; ++i) {
1693 try {
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001694 ApplicationInfo info = mPackageManager.getApplicationInfo(
Jason Monk41314972014-03-03 16:11:30 -05001695 appList.get(i).activityInfo.packageName, 0);
1696 if ((info.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
1697 if (systemResolve != null) {
1698 return null;
1699 } else {
1700 systemResolve = appList.get(i);
1701 }
1702 }
1703 } catch (PackageManager.NameNotFoundException e) {
1704 Log.w(TAG, "Unable to get info about resolve results", e);
1705 return null;
1706 }
1707 }
1708 return systemResolve;
1709 }
1710
1711 private boolean wouldLaunchResolverActivity(ResolveInfo resolved,
1712 List<ResolveInfo> appList) {
1713 // If the list contains the above resolved activity, then it can't be
1714 // ResolverActivity itself.
1715 for (int i = 0; i < appList.size(); ++i) {
1716 ResolveInfo tmp = appList.get(i);
1717 if (tmp.activityInfo.name.equals(resolved.activityInfo.name)
1718 && tmp.activityInfo.packageName.equals(resolved.activityInfo.packageName)) {
1719 return false;
1720 }
1721 }
1722 return true;
1723 }
1724
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001725 private long addAppShortcut(SQLiteDatabase db, ContentValues values,
1726 XmlResourceParser parser) {
1727 final String packageName = getAttributeValue(parser, ATTR_PACKAGE_NAME);
1728 final String className = getAttributeValue(parser, ATTR_CLASS_NAME);
1729 final String uri = getAttributeValue(parser, ATTR_URI);
1730
1731 if (!TextUtils.isEmpty(packageName) && !TextUtils.isEmpty(className)) {
Jason Monk41314972014-03-03 16:11:30 -05001732 ActivityInfo info;
Jason Monk41314972014-03-03 16:11:30 -05001733 try {
1734 ComponentName cn;
1735 try {
1736 cn = new ComponentName(packageName, className);
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001737 info = mPackageManager.getActivityInfo(cn, 0);
Jason Monk41314972014-03-03 16:11:30 -05001738 } catch (PackageManager.NameNotFoundException nnfe) {
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001739 String[] packages = mPackageManager.currentToCanonicalPackageNames(
Jason Monk41314972014-03-03 16:11:30 -05001740 new String[] { packageName });
1741 cn = new ComponentName(packages[0], className);
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001742 info = mPackageManager.getActivityInfo(cn, 0);
Jason Monk41314972014-03-03 16:11:30 -05001743 }
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001744 final Intent intent = buildMainIntent();
Jason Monk41314972014-03-03 16:11:30 -05001745 intent.setComponent(cn);
1746 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
1747 Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
1748
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001749 return addAppShortcut(db, values, info.loadLabel(mPackageManager).toString(),
Jason Monk41314972014-03-03 16:11:30 -05001750 intent);
1751 } catch (PackageManager.NameNotFoundException e) {
1752 Log.w(TAG, "Unable to add favorite: " + packageName +
1753 "/" + className, e);
1754 }
1755 return -1;
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001756 } else if (!TextUtils.isEmpty(uri)) {
Jason Monk41314972014-03-03 16:11:30 -05001757 // If no component specified try to find a shortcut to add from the URI.
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001758 return addAppShortcutByUri(db, values, uri);
Jason Monk41314972014-03-03 16:11:30 -05001759 } else {
1760 Log.e(TAG, "Skipping invalid <favorite> with no component or uri");
1761 return -1;
1762 }
1763 }
1764
1765 private long addAppShortcut(SQLiteDatabase db, ContentValues values, String title,
1766 Intent intent) {
1767 long id = generateNewItemId();
1768 values.put(Favorites.INTENT, intent.toUri(0));
1769 values.put(Favorites.TITLE, title);
1770 values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_APPLICATION);
1771 values.put(Favorites.SPANX, 1);
1772 values.put(Favorites.SPANY, 1);
1773 values.put(Favorites._ID, id);
1774 if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values) < 0) {
1775 return -1;
1776 } else {
1777 return id;
1778 }
Adam Cohen228da5a2011-07-27 22:23:47 -07001779 }
1780
1781 private long addFolder(SQLiteDatabase db, ContentValues values) {
1782 values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_FOLDER);
1783 values.put(Favorites.SPANX, 1);
1784 values.put(Favorites.SPANY, 1);
Adam Cohendcd297f2013-06-18 13:13:40 -07001785 long id = generateNewItemId();
Adam Cohen228da5a2011-07-27 22:23:47 -07001786 values.put(Favorites._ID, id);
1787 if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values) <= 0) {
1788 return -1;
1789 } else {
1790 return id;
1791 }
The Android Open Source Projectf96811c2009-03-18 17:39:48 -07001792 }
1793
Bjorn Bringertcd8fec02010-01-14 13:26:43 +00001794 private ComponentName getSearchWidgetProvider() {
1795 SearchManager searchManager =
1796 (SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE);
1797 ComponentName searchComponent = searchManager.getGlobalSearchActivity();
1798 if (searchComponent == null) return null;
1799 return getProviderInPackage(searchComponent.getPackageName());
1800 }
1801
1802 /**
1803 * Gets an appwidget provider from the given package. If the package contains more than
1804 * one appwidget provider, an arbitrary one is returned.
1805 */
1806 private ComponentName getProviderInPackage(String packageName) {
1807 AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext);
1808 List<AppWidgetProviderInfo> providers = appWidgetManager.getInstalledProviders();
1809 if (providers == null) return null;
1810 final int providerCount = providers.size();
1811 for (int i = 0; i < providerCount; i++) {
1812 ComponentName provider = providers.get(i).provider;
1813 if (provider != null && provider.getPackageName().equals(packageName)) {
1814 return provider;
1815 }
1816 }
1817 return null;
1818 }
1819
Adam Cohen9b8f51f2014-05-30 15:34:09 -07001820 private boolean addAppWidget(XmlResourceParser parser, int type,
1821 SQLiteDatabase db, ContentValues values)
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001822 throws XmlPullParserException, IOException {
Romain Guy693599f2010-03-23 10:58:18 -07001823
Adam Cohen9b8f51f2014-05-30 15:34:09 -07001824 String packageName = getAttributeValue(parser, ATTR_PACKAGE_NAME);
1825 String className = getAttributeValue(parser, ATTR_CLASS_NAME);
Mike Cleronb87bd162009-10-30 16:36:56 -07001826
1827 if (packageName == null || className == null) {
1828 return false;
1829 }
Romain Guy693599f2010-03-23 10:58:18 -07001830
1831 boolean hasPackage = true;
Mike Cleronb87bd162009-10-30 16:36:56 -07001832 ComponentName cn = new ComponentName(packageName, className);
Romain Guy693599f2010-03-23 10:58:18 -07001833 try {
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001834 mPackageManager.getReceiverInfo(cn, 0);
Romain Guy693599f2010-03-23 10:58:18 -07001835 } catch (Exception e) {
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001836 String[] packages = mPackageManager.currentToCanonicalPackageNames(
Romain Guy693599f2010-03-23 10:58:18 -07001837 new String[] { packageName });
1838 cn = new ComponentName(packages[0], className);
1839 try {
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001840 mPackageManager.getReceiverInfo(cn, 0);
Romain Guy693599f2010-03-23 10:58:18 -07001841 } catch (Exception e1) {
Adam Cohen9b8f51f2014-05-30 15:34:09 -07001842 System.out.println("Can't find widget provider: " + className);
Romain Guy693599f2010-03-23 10:58:18 -07001843 hasPackage = false;
1844 }
1845 }
1846
1847 if (hasPackage) {
Adam Cohen9b8f51f2014-05-30 15:34:09 -07001848 String spanX = getAttributeValue(parser, ATTR_SPAN_X);
1849 String spanY = getAttributeValue(parser, ATTR_SPAN_Y);
1850
1851 values.put(Favorites.SPANX, spanX);
1852 values.put(Favorites.SPANY, spanY);
Winson Chungb3302ae2012-05-01 10:19:14 -07001853
1854 // Read the extras
1855 Bundle extras = new Bundle();
1856 int widgetDepth = parser.getDepth();
1857 while ((type = parser.next()) != XmlPullParser.END_TAG ||
1858 parser.getDepth() > widgetDepth) {
1859 if (type != XmlPullParser.START_TAG) {
1860 continue;
1861 }
1862
Winson Chungb3302ae2012-05-01 10:19:14 -07001863 if (TAG_EXTRA.equals(parser.getName())) {
Adam Cohen9b8f51f2014-05-30 15:34:09 -07001864 String key = getAttributeValue(parser, ATTR_KEY);
1865 String value = getAttributeValue(parser, ATTR_VALUE);
Winson Chungb3302ae2012-05-01 10:19:14 -07001866 if (key != null && value != null) {
1867 extras.putString(key, value);
1868 } else {
1869 throw new RuntimeException("Widget extras must have a key and value");
1870 }
1871 } else {
1872 throw new RuntimeException("Widgets can contain only extras");
1873 }
Winson Chungb3302ae2012-05-01 10:19:14 -07001874 }
1875
Adam Cohen9b8f51f2014-05-30 15:34:09 -07001876 return addAppWidget(db, values, cn, extras);
Romain Guy693599f2010-03-23 10:58:18 -07001877 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -07001878
Romain Guy693599f2010-03-23 10:58:18 -07001879 return false;
Bjorn Bringert7984c942009-12-09 15:38:25 +00001880 }
Adam Cohena043fa82014-07-23 14:49:38 -07001881
Bjorn Bringert7984c942009-12-09 15:38:25 +00001882 private boolean addAppWidget(SQLiteDatabase db, ContentValues values, ComponentName cn,
Adam Cohen9b8f51f2014-05-30 15:34:09 -07001883 Bundle extras) {
Mike Cleronb87bd162009-10-30 16:36:56 -07001884 boolean allocatedAppWidgets = false;
1885 final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext);
1886
1887 try {
1888 int appWidgetId = mAppWidgetHost.allocateAppWidgetId();
Daniel Lehmannc3a80402012-04-23 21:35:11 -07001889
Mike Cleronb87bd162009-10-30 16:36:56 -07001890 values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_APPWIDGET);
Mike Cleronb87bd162009-10-30 16:36:56 -07001891 values.put(Favorites.APPWIDGET_ID, appWidgetId);
Chris Wrend5e66bf2013-09-16 14:02:29 -04001892 values.put(Favorites.APPWIDGET_PROVIDER, cn.flattenToString());
Adam Cohendcd297f2013-06-18 13:13:40 -07001893 values.put(Favorites._ID, generateNewItemId());
Michael Jurkaa8c760d2011-04-28 14:59:33 -07001894 dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values);
Mike Cleronb87bd162009-10-30 16:36:56 -07001895
1896 allocatedAppWidgets = true;
Daniel Lehmannc3a80402012-04-23 21:35:11 -07001897
Michael Jurka8b805b12012-04-18 14:23:14 -07001898 // TODO: need to check return value
1899 appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, cn);
Winson Chungb3302ae2012-05-01 10:19:14 -07001900
1901 // Send a broadcast to configure the widget
1902 if (extras != null && !extras.isEmpty()) {
1903 Intent intent = new Intent(ACTION_APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE);
1904 intent.setComponent(cn);
1905 intent.putExtras(extras);
1906 intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
1907 mContext.sendBroadcast(intent);
1908 }
Mike Cleronb87bd162009-10-30 16:36:56 -07001909 } catch (RuntimeException ex) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -08001910 Log.e(TAG, "Problem allocating appWidgetId", ex);
Mike Cleronb87bd162009-10-30 16:36:56 -07001911 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -07001912
Mike Cleronb87bd162009-10-30 16:36:56 -07001913 return allocatedAppWidgets;
1914 }
Adam Cohen228da5a2011-07-27 22:23:47 -07001915
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001916 private long addUriShortcut(SQLiteDatabase db, ContentValues values, Resources res,
1917 XmlResourceParser parser) {
1918 final int iconResId = getAttributeResourceValue(parser, ATTR_ICON, 0);
1919 final int titleResId = getAttributeResourceValue(parser, ATTR_TITLE, 0);
Mike Cleronb87bd162009-10-30 16:36:56 -07001920
Romain Guy7eb9e5e2009-12-02 20:10:07 -08001921 Intent intent;
Mike Cleronb87bd162009-10-30 16:36:56 -07001922 String uri = null;
1923 try {
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001924 uri = getAttributeValue(parser, ATTR_URI);
Mike Cleronb87bd162009-10-30 16:36:56 -07001925 intent = Intent.parseUri(uri, 0);
1926 } catch (URISyntaxException e) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -08001927 Log.w(TAG, "Shortcut has malformed uri: " + uri);
Adam Cohen228da5a2011-07-27 22:23:47 -07001928 return -1; // Oh well
Mike Cleronb87bd162009-10-30 16:36:56 -07001929 }
1930
1931 if (iconResId == 0 || titleResId == 0) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -08001932 Log.w(TAG, "Shortcut is missing title or icon resource ID");
Adam Cohen228da5a2011-07-27 22:23:47 -07001933 return -1;
Mike Cleronb87bd162009-10-30 16:36:56 -07001934 }
1935
Adam Cohendcd297f2013-06-18 13:13:40 -07001936 long id = generateNewItemId();
Mike Cleronb87bd162009-10-30 16:36:56 -07001937 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1938 values.put(Favorites.INTENT, intent.toUri(0));
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001939 values.put(Favorites.TITLE, res.getString(titleResId));
Mike Cleronb87bd162009-10-30 16:36:56 -07001940 values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_SHORTCUT);
1941 values.put(Favorites.SPANX, 1);
1942 values.put(Favorites.SPANY, 1);
1943 values.put(Favorites.ICON_TYPE, Favorites.ICON_TYPE_RESOURCE);
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001944 values.put(Favorites.ICON_PACKAGE, res.getResourcePackageName(iconResId));
1945 values.put(Favorites.ICON_RESOURCE, res.getResourceName(iconResId));
Adam Cohen228da5a2011-07-27 22:23:47 -07001946 values.put(Favorites._ID, id);
Mike Cleronb87bd162009-10-30 16:36:56 -07001947
Adam Cohen228da5a2011-07-27 22:23:47 -07001948 if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values) < 0) {
1949 return -1;
1950 }
1951 return id;
Mike Cleronb87bd162009-10-30 16:36:56 -07001952 }
Dan Sandlerd5024042014-01-09 15:01:33 -05001953
Sunny Goyal0fe505b2014-08-06 09:55:36 -07001954 private void migrateLauncher2Shortcuts(SQLiteDatabase db, Uri uri) {
Dan Sandlerd5024042014-01-09 15:01:33 -05001955 final ContentResolver resolver = mContext.getContentResolver();
1956 Cursor c = null;
1957 int count = 0;
1958 int curScreen = 0;
1959
1960 try {
1961 c = resolver.query(uri, null, null, null, "title ASC");
1962 } catch (Exception e) {
1963 // Ignore
1964 }
1965
Dan Sandlerd5024042014-01-09 15:01:33 -05001966 // We already have a favorites database in the old provider
1967 if (c != null) {
1968 try {
1969 if (c.getCount() > 0) {
1970 final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
1971 final int intentIndex
1972 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.INTENT);
1973 final int titleIndex
1974 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE);
1975 final int iconTypeIndex
1976 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_TYPE);
1977 final int iconIndex
1978 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON);
1979 final int iconPackageIndex
1980 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_PACKAGE);
1981 final int iconResourceIndex
1982 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_RESOURCE);
1983 final int containerIndex
1984 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER);
1985 final int itemTypeIndex
1986 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE);
1987 final int screenIndex
1988 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN);
1989 final int cellXIndex
1990 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX);
1991 final int cellYIndex
1992 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY);
1993 final int uriIndex
1994 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.URI);
1995 final int displayModeIndex
1996 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.DISPLAY_MODE);
Kenny Guyed131872014-04-30 03:02:21 +01001997 final int profileIndex
1998 = c.getColumnIndex(LauncherSettings.Favorites.PROFILE_ID);
Dan Sandlerd5024042014-01-09 15:01:33 -05001999
2000 int i = 0;
2001 int curX = 0;
2002 int curY = 0;
2003
2004 final LauncherAppState app = LauncherAppState.getInstance();
2005 final DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
2006 final int width = (int) grid.numColumns;
2007 final int height = (int) grid.numRows;
2008 final int hotseatWidth = (int) grid.numHotseatIcons;
2009
2010 final HashSet<String> seenIntents = new HashSet<String>(c.getCount());
2011
Adam Cohen72960972014-01-15 18:13:55 -08002012 final ArrayList<ContentValues> shortcuts = new ArrayList<ContentValues>();
2013 final ArrayList<ContentValues> folders = new ArrayList<ContentValues>();
Dan Sandlerab5fa3a2014-03-06 23:48:04 -05002014 final SparseArray<ContentValues> hotseat = new SparseArray<ContentValues>();
Dan Sandlerd5024042014-01-09 15:01:33 -05002015
2016 while (c.moveToNext()) {
2017 final int itemType = c.getInt(itemTypeIndex);
2018 if (itemType != Favorites.ITEM_TYPE_APPLICATION
2019 && itemType != Favorites.ITEM_TYPE_SHORTCUT
2020 && itemType != Favorites.ITEM_TYPE_FOLDER) {
2021 continue;
2022 }
2023
2024 final int cellX = c.getInt(cellXIndex);
2025 final int cellY = c.getInt(cellYIndex);
2026 final int screen = c.getInt(screenIndex);
2027 int container = c.getInt(containerIndex);
2028 final String intentStr = c.getString(intentIndex);
Kenny Guyed131872014-04-30 03:02:21 +01002029
2030 UserManagerCompat userManager = UserManagerCompat.getInstance(mContext);
2031 UserHandleCompat userHandle;
2032 final long userSerialNumber;
2033 if (profileIndex != -1 && !c.isNull(profileIndex)) {
2034 userSerialNumber = c.getInt(profileIndex);
2035 userHandle = userManager.getUserForSerialNumber(userSerialNumber);
2036 } else {
2037 // Default to the serial number of this user, for older
2038 // shortcuts.
2039 userHandle = UserHandleCompat.myUserHandle();
2040 userSerialNumber = userManager.getSerialNumberForUser(userHandle);
2041 }
Dan Sandlerd5024042014-01-09 15:01:33 -05002042 Launcher.addDumpLog(TAG, "migrating \""
Dan Sandlerab5fa3a2014-03-06 23:48:04 -05002043 + c.getString(titleIndex) + "\" ("
2044 + cellX + "," + cellY + "@"
2045 + LauncherSettings.Favorites.containerToString(container)
2046 + "/" + screen
2047 + "): " + intentStr, true);
Dan Sandlerd5024042014-01-09 15:01:33 -05002048
2049 if (itemType != Favorites.ITEM_TYPE_FOLDER) {
Adam Cohen556f6132014-01-15 15:18:08 -08002050
2051 final Intent intent;
2052 final ComponentName cn;
2053 try {
2054 intent = Intent.parseUri(intentStr, 0);
2055 } catch (URISyntaxException e) {
2056 // bogus intent?
2057 Launcher.addDumpLog(TAG,
2058 "skipping invalid intent uri", true);
2059 continue;
2060 }
2061
2062 cn = intent.getComponent();
Dan Sandlerd5024042014-01-09 15:01:33 -05002063 if (TextUtils.isEmpty(intentStr)) {
2064 // no intent? no icon
2065 Launcher.addDumpLog(TAG, "skipping empty intent", true);
2066 continue;
Adam Cohen72960972014-01-15 18:13:55 -08002067 } else if (cn != null &&
Kenny Guyed131872014-04-30 03:02:21 +01002068 !LauncherModel.isValidPackageActivity(mContext, cn,
2069 userHandle)) {
Adam Cohen556f6132014-01-15 15:18:08 -08002070 // component no longer exists.
Adam Cohen72960972014-01-15 18:13:55 -08002071 Launcher.addDumpLog(TAG, "skipping item whose component " +
Adam Cohen556f6132014-01-15 15:18:08 -08002072 "no longer exists.", true);
2073 continue;
Adam Cohen72960972014-01-15 18:13:55 -08002074 } else if (container ==
2075 LauncherSettings.Favorites.CONTAINER_DESKTOP) {
2076 // Dedupe icons directly on the workspace
2077
Adam Cohen556f6132014-01-15 15:18:08 -08002078 // Canonicalize
2079 // the Play Store sets the package parameter, but Launcher
2080 // does not, so we clear that out to keep them the same
2081 intent.setPackage(null);
2082 final String key = intent.toUri(0);
2083 if (seenIntents.contains(key)) {
2084 Launcher.addDumpLog(TAG, "skipping duplicate", true);
Dan Sandlerd5024042014-01-09 15:01:33 -05002085 continue;
Adam Cohen556f6132014-01-15 15:18:08 -08002086 } else {
2087 seenIntents.add(key);
Dan Sandlerd5024042014-01-09 15:01:33 -05002088 }
2089 }
2090 }
2091
2092 ContentValues values = new ContentValues(c.getColumnCount());
2093 values.put(LauncherSettings.Favorites._ID, c.getInt(idIndex));
2094 values.put(LauncherSettings.Favorites.INTENT, intentStr);
2095 values.put(LauncherSettings.Favorites.TITLE, c.getString(titleIndex));
2096 values.put(LauncherSettings.Favorites.ICON_TYPE,
2097 c.getInt(iconTypeIndex));
2098 values.put(LauncherSettings.Favorites.ICON, c.getBlob(iconIndex));
2099 values.put(LauncherSettings.Favorites.ICON_PACKAGE,
2100 c.getString(iconPackageIndex));
2101 values.put(LauncherSettings.Favorites.ICON_RESOURCE,
2102 c.getString(iconResourceIndex));
2103 values.put(LauncherSettings.Favorites.ITEM_TYPE, itemType);
2104 values.put(LauncherSettings.Favorites.APPWIDGET_ID, -1);
2105 values.put(LauncherSettings.Favorites.URI, c.getString(uriIndex));
2106 values.put(LauncherSettings.Favorites.DISPLAY_MODE,
2107 c.getInt(displayModeIndex));
Kenny Guyed131872014-04-30 03:02:21 +01002108 values.put(LauncherSettings.Favorites.PROFILE_ID, userSerialNumber);
Dan Sandlerd5024042014-01-09 15:01:33 -05002109
Dan Sandlerab5fa3a2014-03-06 23:48:04 -05002110 if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
2111 hotseat.put(screen, values);
Dan Sandlerd5024042014-01-09 15:01:33 -05002112 }
2113
2114 if (container != LauncherSettings.Favorites.CONTAINER_DESKTOP) {
2115 // In a folder or in the hotseat, preserve position
2116 values.put(LauncherSettings.Favorites.SCREEN, screen);
2117 values.put(LauncherSettings.Favorites.CELLX, cellX);
2118 values.put(LauncherSettings.Favorites.CELLY, cellY);
2119 } else {
Adam Cohen72960972014-01-15 18:13:55 -08002120 // For items contained directly on one of the workspace screen,
2121 // we'll determine their location (screen, x, y) in a second pass.
Dan Sandlerd5024042014-01-09 15:01:33 -05002122 }
2123
2124 values.put(LauncherSettings.Favorites.CONTAINER, container);
2125
Adam Cohen72960972014-01-15 18:13:55 -08002126 if (itemType != Favorites.ITEM_TYPE_FOLDER) {
2127 shortcuts.add(values);
2128 } else {
2129 folders.add(values);
2130 }
Dan Sandlerd5024042014-01-09 15:01:33 -05002131 }
2132
Dan Sandlerab5fa3a2014-03-06 23:48:04 -05002133 // Now that we have all the hotseat icons, let's go through them left-right
2134 // and assign valid locations for them in the new hotseat
2135 final int N = hotseat.size();
2136 for (int idx=0; idx<N; idx++) {
2137 int hotseatX = hotseat.keyAt(idx);
2138 ContentValues values = hotseat.valueAt(idx);
2139
2140 if (hotseatX == grid.hotseatAllAppsRank) {
2141 // let's drop this in the next available hole in the hotseat
2142 while (++hotseatX < hotseatWidth) {
2143 if (hotseat.get(hotseatX) == null) {
2144 // found a spot! move it here
2145 values.put(LauncherSettings.Favorites.SCREEN,
2146 hotseatX);
2147 break;
2148 }
2149 }
2150 }
2151 if (hotseatX >= hotseatWidth) {
2152 // no room for you in the hotseat? it's off to the desktop with you
2153 values.put(LauncherSettings.Favorites.CONTAINER,
2154 Favorites.CONTAINER_DESKTOP);
2155 }
2156 }
2157
Adam Cohen72960972014-01-15 18:13:55 -08002158 final ArrayList<ContentValues> allItems = new ArrayList<ContentValues>();
2159 // Folders first
2160 allItems.addAll(folders);
2161 // Then shortcuts
2162 allItems.addAll(shortcuts);
2163
2164 // Layout all the folders
2165 for (ContentValues values: allItems) {
2166 if (values.getAsInteger(LauncherSettings.Favorites.CONTAINER) !=
2167 LauncherSettings.Favorites.CONTAINER_DESKTOP) {
2168 // Hotseat items and folder items have already had their
2169 // location information set. Nothing to be done here.
2170 continue;
2171 }
2172 values.put(LauncherSettings.Favorites.SCREEN, curScreen);
2173 values.put(LauncherSettings.Favorites.CELLX, curX);
2174 values.put(LauncherSettings.Favorites.CELLY, curY);
2175 curX = (curX + 1) % width;
2176 if (curX == 0) {
2177 curY = (curY + 1);
2178 }
2179 // Leave the last row of icons blank on every screen
2180 if (curY == height - 1) {
2181 curScreen = (int) generateNewScreenId();
2182 curY = 0;
2183 }
2184 }
2185
2186 if (allItems.size() > 0) {
Dan Sandlerd5024042014-01-09 15:01:33 -05002187 db.beginTransaction();
2188 try {
Adam Cohen72960972014-01-15 18:13:55 -08002189 for (ContentValues row: allItems) {
2190 if (row == null) continue;
2191 if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, row)
Dan Sandlerd5024042014-01-09 15:01:33 -05002192 < 0) {
2193 return;
2194 } else {
2195 count++;
2196 }
2197 }
2198 db.setTransactionSuccessful();
2199 } finally {
2200 db.endTransaction();
2201 }
2202 }
2203
2204 db.beginTransaction();
2205 try {
2206 for (i=0; i<=curScreen; i++) {
2207 final ContentValues values = new ContentValues();
2208 values.put(LauncherSettings.WorkspaceScreens._ID, i);
2209 values.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, i);
2210 if (dbInsertAndCheck(this, db, TABLE_WORKSPACE_SCREENS, null, values)
2211 < 0) {
2212 return;
2213 }
2214 }
2215 db.setTransactionSuccessful();
2216 } finally {
2217 db.endTransaction();
2218 }
2219 }
2220 } finally {
2221 c.close();
2222 }
2223 }
2224
2225 Launcher.addDumpLog(TAG, "migrated " + count + " icons from Launcher2 into "
2226 + (curScreen+1) + " screens", true);
2227
2228 // ensure that new screens are created to hold these icons
2229 setFlagJustLoadedOldDb();
2230
2231 // Update max IDs; very important since we just grabbed IDs from another database
2232 mMaxItemId = initializeMaxItemId(db);
2233 mMaxScreenId = initializeMaxScreenId(db);
2234 if (LOGD) Log.d(TAG, "mMaxItemId: " + mMaxItemId + " mMaxScreenId: " + mMaxScreenId);
2235 }
Mike Cleronb87bd162009-10-30 16:36:56 -07002236 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -07002237
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002238 /**
2239 * Build a query string that will match any row where the column matches
2240 * anything in the values list.
2241 */
Sunny Goyal0fe505b2014-08-06 09:55:36 -07002242 private static String buildOrWhereString(String column, int[] values) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002243 StringBuilder selectWhere = new StringBuilder();
2244 for (int i = values.length - 1; i >= 0; i--) {
2245 selectWhere.append(column).append("=").append(values[i]);
2246 if (i > 0) {
2247 selectWhere.append(" OR ");
2248 }
2249 }
2250 return selectWhere.toString();
2251 }
2252
Jeff Sharkey5aeef582014-04-14 13:26:42 -07002253 /**
2254 * Return attribute value, attempting launcher-specific namespace first
2255 * before falling back to anonymous attribute.
2256 */
Sunny Goyal0fe505b2014-08-06 09:55:36 -07002257 private static String getAttributeValue(XmlResourceParser parser, String attribute) {
Jeff Sharkey5aeef582014-04-14 13:26:42 -07002258 String value = parser.getAttributeValue(
2259 "http://schemas.android.com/apk/res-auto/com.android.launcher3", attribute);
2260 if (value == null) {
2261 value = parser.getAttributeValue(null, attribute);
2262 }
2263 return value;
2264 }
2265
2266 /**
2267 * Return attribute resource value, attempting launcher-specific namespace
2268 * first before falling back to anonymous attribute.
2269 */
Sunny Goyal0fe505b2014-08-06 09:55:36 -07002270 private static int getAttributeResourceValue(XmlResourceParser parser, String attribute,
Jeff Sharkey5aeef582014-04-14 13:26:42 -07002271 int defaultValue) {
2272 int value = parser.getAttributeResourceValue(
2273 "http://schemas.android.com/apk/res-auto/com.android.launcher3", attribute,
2274 defaultValue);
2275 if (value == defaultValue) {
2276 value = parser.getAttributeResourceValue(null, attribute, defaultValue);
2277 }
2278 return value;
2279 }
2280
2281 private static void copyInteger(ContentValues from, ContentValues to, String key) {
2282 to.put(key, from.getAsInteger(key));
2283 }
2284
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002285 static class SqlArguments {
2286 public final String table;
2287 public final String where;
2288 public final String[] args;
2289
2290 SqlArguments(Uri url, String where, String[] args) {
2291 if (url.getPathSegments().size() == 1) {
2292 this.table = url.getPathSegments().get(0);
2293 this.where = where;
2294 this.args = args;
2295 } else if (url.getPathSegments().size() != 2) {
2296 throw new IllegalArgumentException("Invalid URI: " + url);
2297 } else if (!TextUtils.isEmpty(where)) {
2298 throw new UnsupportedOperationException("WHERE clause not supported: " + url);
2299 } else {
2300 this.table = url.getPathSegments().get(0);
Daniel Lehmannc3a80402012-04-23 21:35:11 -07002301 this.where = "_id=" + ContentUris.parseId(url);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002302 this.args = null;
2303 }
2304 }
2305
2306 SqlArguments(Uri url) {
2307 if (url.getPathSegments().size() == 1) {
2308 table = url.getPathSegments().get(0);
2309 where = null;
2310 args = null;
2311 } else {
2312 throw new IllegalArgumentException("Invalid URI: " + url);
2313 }
2314 }
2315 }
Sunny Goyal0fe505b2014-08-06 09:55:36 -07002316
2317 static interface WorkspaceLoader {
2318 /**
2319 * @param screenIds A mutable list of screen its
2320 * @return the number of workspace items added.
2321 */
2322 int loadLayout(SQLiteDatabase db, ArrayList<Long> screenIds);
2323 }
2324
2325 private static class SimpleWorkspaceLoader implements WorkspaceLoader {
2326 private final Resources mRes;
2327 private final int mWorkspaceId;
2328 private final DatabaseHelper mHelper;
2329
2330 SimpleWorkspaceLoader(DatabaseHelper helper, Resources res, int workspaceId) {
2331 mHelper = helper;
2332 mRes = res;
2333 mWorkspaceId = workspaceId;
2334 }
2335
2336 @Override
2337 public int loadLayout(SQLiteDatabase db, ArrayList<Long> screenIds) {
2338 return mHelper.loadFavoritesRecursive(db, mRes, mWorkspaceId, screenIds);
2339 }
2340 }
Adam Cohen72960972014-01-15 18:13:55 -08002341}