blob: 670780af08cda8ab7d588cd5f4e34a9900997c2c [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 Cohena043fa82014-07-23 14:49:38 -07001294 }
1295 } catch (RuntimeException e) {
1296 Log.e(TAG, "Failed to initialize external widget", e);
Adam Coheneb1ac422014-10-14 08:55:28 -07001297 return false;
Adam Cohena043fa82014-07-23 14:49:38 -07001298 }
Adam Coheneb1ac422014-10-14 08:55:28 -07001299 } else {
1300 return false;
Adam Cohena043fa82014-07-23 14:49:38 -07001301 }
1302 }
Adam Cohen7ec3bbf2014-07-31 00:09:45 -07001303
1304 // Add screen id if not present
1305 long screenId = values.getAsLong(LauncherSettings.Favorites.SCREEN);
1306 if (!addScreenIdIfNecessary(screenId)) {
1307 return false;
1308 }
Adam Cohena043fa82014-07-23 14:49:38 -07001309 return true;
1310 }
1311
Adam Cohen7ec3bbf2014-07-31 00:09:45 -07001312 // Returns true of screen id exists, or if successfully added
1313 private boolean addScreenIdIfNecessary(long screenId) {
1314 if (!hasScreenId(screenId)) {
1315 int rank = getMaxScreenRank() + 1;
1316
1317 ContentValues v = new ContentValues();
1318 v.put(LauncherSettings.WorkspaceScreens._ID, screenId);
1319 v.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, rank);
1320 if (dbInsertAndCheck(this, getWritableDatabase(),
1321 TABLE_WORKSPACE_SCREENS, null, v) < 0) {
1322 return false;
1323 }
1324 }
1325 return true;
1326 }
1327
1328 private boolean hasScreenId(long screenId) {
1329 SQLiteDatabase db = getWritableDatabase();
1330 Cursor c = db.rawQuery("SELECT * FROM " + TABLE_WORKSPACE_SCREENS + " WHERE "
1331 + LauncherSettings.WorkspaceScreens._ID + " = " + screenId, null);
1332 if (c != null) {
1333 int count = c.getCount();
1334 c.close();
1335 return count > 0;
1336 } else {
1337 return false;
1338 }
1339 }
1340
1341 private int getMaxScreenRank() {
1342 SQLiteDatabase db = getWritableDatabase();
1343 Cursor c = db.rawQuery("SELECT MAX(" + LauncherSettings.WorkspaceScreens.SCREEN_RANK
1344 + ") FROM " + TABLE_WORKSPACE_SCREENS, null);
1345
1346 // get the result
1347 final int maxRankIndex = 0;
1348 int rank = -1;
1349 if (c != null && c.moveToNext()) {
1350 rank = c.getInt(maxRankIndex);
1351 }
1352 if (c != null) {
1353 c.close();
1354 }
1355
1356 return rank;
1357 }
1358
Michael Jurka8b805b12012-04-18 14:23:14 -07001359 private static final void beginDocument(XmlPullParser parser, String firstElementName)
1360 throws XmlPullParserException, IOException {
1361 int type;
Michael Jurka9bc8eba2012-05-21 20:36:44 -07001362 while ((type = parser.next()) != XmlPullParser.START_TAG
1363 && type != XmlPullParser.END_DOCUMENT) {
Michael Jurka8b805b12012-04-18 14:23:14 -07001364 ;
1365 }
1366
Michael Jurka9bc8eba2012-05-21 20:36:44 -07001367 if (type != XmlPullParser.START_TAG) {
Michael Jurka8b805b12012-04-18 14:23:14 -07001368 throw new XmlPullParserException("No start tag found");
1369 }
1370
1371 if (!parser.getName().equals(firstElementName)) {
1372 throw new XmlPullParserException("Unexpected start tag: found " + parser.getName() +
1373 ", expected " + firstElementName);
1374 }
1375 }
1376
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001377 private static Intent buildMainIntent() {
1378 Intent intent = new Intent(Intent.ACTION_MAIN, null);
1379 intent.addCategory(Intent.CATEGORY_LAUNCHER);
1380 return intent;
1381 }
1382
Sunny Goyal0fe505b2014-08-06 09:55:36 -07001383 private int loadFavorites(SQLiteDatabase db, WorkspaceLoader loader) {
Adam Cohen71483f42014-05-15 14:04:01 -07001384 ArrayList<Long> screenIds = new ArrayList<Long>();
Sunny Goyal0fe505b2014-08-06 09:55:36 -07001385 // TODO: Use multiple loaders with fall-back and transaction.
1386 int count = loader.loadLayout(db, screenIds);
Adam Cohen71483f42014-05-15 14:04:01 -07001387
1388 // Add the screens specified by the items above
1389 Collections.sort(screenIds);
1390 int rank = 0;
1391 ContentValues values = new ContentValues();
1392 for (Long id : screenIds) {
1393 values.clear();
1394 values.put(LauncherSettings.WorkspaceScreens._ID, id);
1395 values.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, rank);
1396 if (dbInsertAndCheck(this, db, TABLE_WORKSPACE_SCREENS, null, values) < 0) {
1397 throw new RuntimeException("Failed initialize screen table"
1398 + "from default layout");
1399 }
1400 rank++;
1401 }
1402
1403 // Ensure that the max ids are initialized
1404 mMaxItemId = initializeMaxItemId(db);
1405 mMaxScreenId = initializeMaxScreenId(db);
Adam Cohen7ec3bbf2014-07-31 00:09:45 -07001406
Adam Cohen71483f42014-05-15 14:04:01 -07001407 return count;
1408 }
1409
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001410 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001411 * Loads the default set of favorite packages from an xml file.
1412 *
1413 * @param db The database to write the values into
Winson Chung3d503fb2011-07-13 17:25:49 -07001414 * @param filterContainerId The specific container id of items to load
Adam Cohen71483f42014-05-15 14:04:01 -07001415 * @param the set of screenIds which are used by the favorites
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001416 */
Adam Cohen9b8f51f2014-05-30 15:34:09 -07001417 private int loadFavoritesRecursive(SQLiteDatabase db, Resources res, int workspaceResourceId,
Adam Cohen71483f42014-05-15 14:04:01 -07001418 ArrayList<Long> screenIds) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001419
Adam Cohen71483f42014-05-15 14:04:01 -07001420 ContentValues values = new ContentValues();
Daniel Sandler57dac262013-10-03 13:28:36 -04001421 if (LOGD) Log.v(TAG, String.format("Loading favorites from resid=0x%08x", workspaceResourceId));
1422
Adam Cohen71483f42014-05-15 14:04:01 -07001423 int count = 0;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001424 try {
Adam Cohen9b8f51f2014-05-30 15:34:09 -07001425 XmlResourceParser parser = res.getXml(workspaceResourceId);
Michael Jurka8b805b12012-04-18 14:23:14 -07001426 beginDocument(parser, TAG_FAVORITES);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001427
The Android Open Source Projectf96811c2009-03-18 17:39:48 -07001428 final int depth = parser.getDepth();
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001429
The Android Open Source Projectf96811c2009-03-18 17:39:48 -07001430 int type;
1431 while (((type = parser.next()) != XmlPullParser.END_TAG ||
1432 parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
1433
1434 if (type != XmlPullParser.START_TAG) {
1435 continue;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001436 }
1437
The Android Open Source Projectf96811c2009-03-18 17:39:48 -07001438 boolean added = false;
1439 final String name = parser.getName();
1440
Daniel Sandler57dac262013-10-03 13:28:36 -04001441 if (TAG_INCLUDE.equals(name)) {
Daniel Sandler57dac262013-10-03 13:28:36 -04001442
Adam Cohen9b8f51f2014-05-30 15:34:09 -07001443 final int resId = getAttributeResourceValue(parser, ATTR_WORKSPACE, 0);
Daniel Sandler57dac262013-10-03 13:28:36 -04001444
1445 if (LOGD) Log.v(TAG, String.format(("%" + (2*(depth+1)) + "s<include workspace=%08x>"),
1446 "", resId));
1447
1448 if (resId != 0 && resId != workspaceResourceId) {
1449 // recursively load some more favorites, why not?
Adam Cohen9b8f51f2014-05-30 15:34:09 -07001450 count += loadFavoritesRecursive(db, res, resId, screenIds);
Daniel Sandler57dac262013-10-03 13:28:36 -04001451 added = false;
Daniel Sandler57dac262013-10-03 13:28:36 -04001452 } else {
1453 Log.w(TAG, String.format("Skipping <include workspace=0x%08x>", resId));
1454 }
1455
Daniel Sandler57dac262013-10-03 13:28:36 -04001456 if (LOGD) Log.v(TAG, String.format(("%" + (2*(depth+1)) + "s</include>"), ""));
1457 continue;
1458 }
1459
1460 // Assuming it's a <favorite> at this point
Winson Chung3d503fb2011-07-13 17:25:49 -07001461 long container = LauncherSettings.Favorites.CONTAINER_DESKTOP;
Adam Cohen9b8f51f2014-05-30 15:34:09 -07001462 String strContainer = getAttributeValue(parser, ATTR_CONTAINER);
1463 if (strContainer != null) {
1464 container = Long.valueOf(strContainer);
Winson Chung3d503fb2011-07-13 17:25:49 -07001465 }
The Android Open Source Projectf96811c2009-03-18 17:39:48 -07001466
Adam Cohen9b8f51f2014-05-30 15:34:09 -07001467 String screen = getAttributeValue(parser, ATTR_SCREEN);
1468 String x = getAttributeValue(parser, ATTR_X);
1469 String y = getAttributeValue(parser, ATTR_Y);
Winson Chung6d092682011-11-16 18:43:26 -08001470
Winson Chung6d092682011-11-16 18:43:26 -08001471 values.clear();
1472 values.put(LauncherSettings.Favorites.CONTAINER, container);
1473 values.put(LauncherSettings.Favorites.SCREEN, screen);
1474 values.put(LauncherSettings.Favorites.CELLX, x);
1475 values.put(LauncherSettings.Favorites.CELLY, y);
1476
Daniel Sandler57dac262013-10-03 13:28:36 -04001477 if (LOGD) {
Adam Cohen9b8f51f2014-05-30 15:34:09 -07001478 final String title = getAttributeValue(parser, ATTR_TITLE);
1479 final String pkg = getAttributeValue(parser, ATTR_PACKAGE_NAME);
Daniel Sandler57dac262013-10-03 13:28:36 -04001480 final String something = title != null ? title : pkg;
1481 Log.v(TAG, String.format(
1482 ("%" + (2*(depth+1)) + "s<%s%s c=%d s=%s x=%s y=%s>"),
1483 "", name,
1484 (something == null ? "" : (" \"" + something + "\"")),
1485 container, screen, x, y));
1486 }
1487
Winson Chung6d092682011-11-16 18:43:26 -08001488 if (TAG_FAVORITE.equals(name)) {
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001489 long id = addAppShortcut(db, values, parser);
Winson Chung6d092682011-11-16 18:43:26 -08001490 added = id >= 0;
Winson Chung6d092682011-11-16 18:43:26 -08001491 } else if (TAG_APPWIDGET.equals(name)) {
Adam Cohen9b8f51f2014-05-30 15:34:09 -07001492 added = addAppWidget(parser, type, db, values);
Winson Chung6d092682011-11-16 18:43:26 -08001493 } else if (TAG_SHORTCUT.equals(name)) {
Adam Cohen9b8f51f2014-05-30 15:34:09 -07001494 long id = addUriShortcut(db, values, res, parser);
Winson Chung6d092682011-11-16 18:43:26 -08001495 added = id >= 0;
Jason Monk41314972014-03-03 16:11:30 -05001496 } else if (TAG_RESOLVE.equals(name)) {
1497 // This looks through the contained favorites (or meta-favorites) and
1498 // attempts to add them as shortcuts in the fallback group's location
1499 // until one is added successfully.
1500 added = false;
1501 final int groupDepth = parser.getDepth();
1502 while ((type = parser.next()) != XmlPullParser.END_TAG ||
1503 parser.getDepth() > groupDepth) {
1504 if (type != XmlPullParser.START_TAG) {
1505 continue;
1506 }
1507 final String fallback_item_name = parser.getName();
Jason Monk41314972014-03-03 16:11:30 -05001508 if (!added) {
1509 if (TAG_FAVORITE.equals(fallback_item_name)) {
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001510 final long id = addAppShortcut(db, values, parser);
Jason Monk41314972014-03-03 16:11:30 -05001511 added = id >= 0;
1512 } else {
Adam Cohen9b8f51f2014-05-30 15:34:09 -07001513 Log.e(TAG, "Fallback groups can contain only favorites, found "
1514 + fallback_item_name);
Jason Monk41314972014-03-03 16:11:30 -05001515 }
1516 }
Jason Monk41314972014-03-03 16:11:30 -05001517 }
Winson Chung6d092682011-11-16 18:43:26 -08001518 } else if (TAG_FOLDER.equals(name)) {
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001519 // Folder contents are nested in this XML file
Adam Cohen43f3ca02014-07-25 13:24:09 -07001520 added = loadFolder(db, values, res, parser);
Winson Chung3d503fb2011-07-13 17:25:49 -07001521
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001522 } else if (TAG_PARTNER_FOLDER.equals(name)) {
1523 // Folder contents come from an external XML resource
1524 final Partner partner = Partner.get(mPackageManager);
1525 if (partner != null) {
1526 final Resources partnerRes = partner.getResources();
Adam Cohen4ae96ce2014-08-29 15:05:48 -07001527 final int resId = partnerRes.getIdentifier(Partner.RES_FOLDER,
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001528 "xml", partner.getPackageName());
1529 if (resId != 0) {
1530 final XmlResourceParser partnerParser = partnerRes.getXml(resId);
1531 beginDocument(partnerParser, TAG_FOLDER);
1532 added = loadFolder(db, values, partnerRes, partnerParser);
Winson Chung6d092682011-11-16 18:43:26 -08001533 }
Winson Chung3d503fb2011-07-13 17:25:49 -07001534 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001535 }
Adam Cohen71483f42014-05-15 14:04:01 -07001536 if (added) {
1537 long screenId = Long.parseLong(screen);
1538 // Keep track of the set of screens which need to be added to the db.
1539 if (!screenIds.contains(screenId) &&
1540 container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
1541 screenIds.add(screenId);
1542 }
1543 count++;
1544 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001545 }
1546 } catch (XmlPullParserException e) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -08001547 Log.w(TAG, "Got exception parsing favorites.", e);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001548 } catch (IOException e) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -08001549 Log.w(TAG, "Got exception parsing favorites.", e);
Winson Chung3d503fb2011-07-13 17:25:49 -07001550 } catch (RuntimeException e) {
1551 Log.w(TAG, "Got exception parsing favorites.", e);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001552 }
Adam Cohen71483f42014-05-15 14:04:01 -07001553 return count;
The Android Open Source Projectf96811c2009-03-18 17:39:48 -07001554 }
1555
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001556 /**
Adam Coheneb1ac422014-10-14 08:55:28 -07001557 * Parse folder items starting at {@link XmlPullParser} location. Allow recursive
1558 * includes of items.
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001559 */
Adam Coheneb1ac422014-10-14 08:55:28 -07001560 private void addToFolder(SQLiteDatabase db, Resources res, XmlResourceParser parser,
1561 ArrayList<Long> folderItems, long folderId) throws IOException, XmlPullParserException {
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001562 int type;
1563 int folderDepth = parser.getDepth();
1564 while ((type = parser.next()) != XmlPullParser.END_TAG ||
1565 parser.getDepth() > folderDepth) {
1566 if (type != XmlPullParser.START_TAG) {
1567 continue;
1568 }
1569 final String tag = parser.getName();
1570
1571 final ContentValues childValues = new ContentValues();
1572 childValues.put(LauncherSettings.Favorites.CONTAINER, folderId);
1573
1574 if (LOGD) {
1575 final String pkg = getAttributeValue(parser, ATTR_PACKAGE_NAME);
1576 final String uri = getAttributeValue(parser, ATTR_URI);
1577 Log.v(TAG, String.format(("%" + (2*(folderDepth+1)) + "s<%s \"%s\">"), "",
1578 tag, uri != null ? uri : pkg));
1579 }
1580
1581 if (TAG_FAVORITE.equals(tag) && folderId >= 0) {
1582 final long id = addAppShortcut(db, childValues, parser);
1583 if (id >= 0) {
1584 folderItems.add(id);
1585 }
1586 } else if (TAG_SHORTCUT.equals(tag) && folderId >= 0) {
1587 final long id = addUriShortcut(db, childValues, res, parser);
1588 if (id >= 0) {
1589 folderItems.add(id);
1590 }
Adam Coheneb1ac422014-10-14 08:55:28 -07001591 } else if (TAG_INCLUDE.equals(tag) && folderId >= 0) {
1592 addToFolder(db, res, parser, folderItems, folderId);
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001593 } else {
1594 throw new RuntimeException("Folders can contain only shortcuts");
1595 }
1596 }
Adam Coheneb1ac422014-10-14 08:55:28 -07001597 }
1598
1599 /**
1600 * Parse folder starting at current {@link XmlPullParser} location.
1601 */
1602 private boolean loadFolder(SQLiteDatabase db, ContentValues values, Resources res,
1603 XmlResourceParser parser) throws IOException, XmlPullParserException {
1604 final String title;
1605 final int titleResId = getAttributeResourceValue(parser, ATTR_TITLE, 0);
1606 if (titleResId != 0) {
1607 title = res.getString(titleResId);
1608 } else {
1609 title = mContext.getResources().getString(R.string.folder_name);
1610 }
1611
1612 values.put(LauncherSettings.Favorites.TITLE, title);
1613 long folderId = addFolder(db, values);
1614 boolean added = folderId >= 0;
1615
1616 ArrayList<Long> folderItems = new ArrayList<Long>();
1617 addToFolder(db, res, parser, folderItems, folderId);
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001618
1619 // We can only have folders with >= 2 items, so we need to remove the
1620 // folder and clean up if less than 2 items were included, or some
1621 // failed to add, and less than 2 were actually added
1622 if (folderItems.size() < 2 && folderId >= 0) {
1623 // Delete the folder
1624 deleteId(db, folderId);
1625
1626 // If we have a single item, promote it to where the folder
1627 // would have been.
1628 if (folderItems.size() == 1) {
1629 final ContentValues childValues = new ContentValues();
1630 copyInteger(values, childValues, LauncherSettings.Favorites.CONTAINER);
1631 copyInteger(values, childValues, LauncherSettings.Favorites.SCREEN);
1632 copyInteger(values, childValues, LauncherSettings.Favorites.CELLX);
1633 copyInteger(values, childValues, LauncherSettings.Favorites.CELLY);
1634
1635 final long id = folderItems.get(0);
1636 db.update(TABLE_FAVORITES, childValues,
1637 LauncherSettings.Favorites._ID + "=" + id, null);
1638 } else {
1639 added = false;
1640 }
1641 }
1642 return added;
1643 }
1644
Jason Monk41314972014-03-03 16:11:30 -05001645 // A meta shortcut attempts to resolve an intent specified as a URI in the XML, if a
1646 // logical choice for what shortcut should be used for that intent exists, then it is
1647 // added. Otherwise add nothing.
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001648 private long addAppShortcutByUri(SQLiteDatabase db, ContentValues values,
1649 String intentUri) {
Jason Monk41314972014-03-03 16:11:30 -05001650 Intent metaIntent;
The Android Open Source Projectf96811c2009-03-18 17:39:48 -07001651 try {
Jason Monk41314972014-03-03 16:11:30 -05001652 metaIntent = Intent.parseUri(intentUri, 0);
1653 } catch (URISyntaxException e) {
1654 Log.e(TAG, "Unable to add meta-favorite: " + intentUri, e);
1655 return -1;
1656 }
1657
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001658 ResolveInfo resolved = mPackageManager.resolveActivity(metaIntent,
Jason Monk41314972014-03-03 16:11:30 -05001659 PackageManager.MATCH_DEFAULT_ONLY);
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001660 final List<ResolveInfo> appList = mPackageManager.queryIntentActivities(
Jason Monk41314972014-03-03 16:11:30 -05001661 metaIntent, PackageManager.MATCH_DEFAULT_ONLY);
1662
1663 // Verify that the result is an app and not just the resolver dialog asking which
1664 // app to use.
1665 if (wouldLaunchResolverActivity(resolved, appList)) {
1666 // If only one of the results is a system app then choose that as the default.
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001667 final ResolveInfo systemApp = getSingleSystemActivity(appList);
Jason Monk41314972014-03-03 16:11:30 -05001668 if (systemApp == null) {
1669 // There is no logical choice for this meta-favorite, so rather than making
1670 // a bad choice just add nothing.
1671 Log.w(TAG, "No preference or single system activity found for "
1672 + metaIntent.toString());
Adam Cohen228da5a2011-07-27 22:23:47 -07001673 return -1;
1674 }
Jason Monk41314972014-03-03 16:11:30 -05001675 resolved = systemApp;
The Android Open Source Projectf96811c2009-03-18 17:39:48 -07001676 }
Jason Monk41314972014-03-03 16:11:30 -05001677 final ActivityInfo info = resolved.activityInfo;
Jason Monkc3009c02014-08-20 09:41:21 -04001678 final Intent intent = mPackageManager.getLaunchIntentForPackage(info.packageName);
1679 if (intent == null) {
1680 return -1;
1681 }
Jason Monk41314972014-03-03 16:11:30 -05001682 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
1683 Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
1684
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001685 return addAppShortcut(db, values, info.loadLabel(mPackageManager).toString(), intent);
Jason Monk41314972014-03-03 16:11:30 -05001686 }
1687
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001688 private ResolveInfo getSingleSystemActivity(List<ResolveInfo> appList) {
Jason Monk41314972014-03-03 16:11:30 -05001689 ResolveInfo systemResolve = null;
1690 final int N = appList.size();
1691 for (int i = 0; i < N; ++i) {
1692 try {
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001693 ApplicationInfo info = mPackageManager.getApplicationInfo(
Jason Monk41314972014-03-03 16:11:30 -05001694 appList.get(i).activityInfo.packageName, 0);
1695 if ((info.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
1696 if (systemResolve != null) {
1697 return null;
1698 } else {
1699 systemResolve = appList.get(i);
1700 }
1701 }
1702 } catch (PackageManager.NameNotFoundException e) {
1703 Log.w(TAG, "Unable to get info about resolve results", e);
1704 return null;
1705 }
1706 }
1707 return systemResolve;
1708 }
1709
1710 private boolean wouldLaunchResolverActivity(ResolveInfo resolved,
1711 List<ResolveInfo> appList) {
1712 // If the list contains the above resolved activity, then it can't be
1713 // ResolverActivity itself.
1714 for (int i = 0; i < appList.size(); ++i) {
1715 ResolveInfo tmp = appList.get(i);
1716 if (tmp.activityInfo.name.equals(resolved.activityInfo.name)
1717 && tmp.activityInfo.packageName.equals(resolved.activityInfo.packageName)) {
1718 return false;
1719 }
1720 }
1721 return true;
1722 }
1723
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001724 private long addAppShortcut(SQLiteDatabase db, ContentValues values,
1725 XmlResourceParser parser) {
1726 final String packageName = getAttributeValue(parser, ATTR_PACKAGE_NAME);
1727 final String className = getAttributeValue(parser, ATTR_CLASS_NAME);
1728 final String uri = getAttributeValue(parser, ATTR_URI);
1729
1730 if (!TextUtils.isEmpty(packageName) && !TextUtils.isEmpty(className)) {
Jason Monk41314972014-03-03 16:11:30 -05001731 ActivityInfo info;
Jason Monk41314972014-03-03 16:11:30 -05001732 try {
1733 ComponentName cn;
1734 try {
1735 cn = new ComponentName(packageName, className);
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001736 info = mPackageManager.getActivityInfo(cn, 0);
Jason Monk41314972014-03-03 16:11:30 -05001737 } catch (PackageManager.NameNotFoundException nnfe) {
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001738 String[] packages = mPackageManager.currentToCanonicalPackageNames(
Jason Monk41314972014-03-03 16:11:30 -05001739 new String[] { packageName });
1740 cn = new ComponentName(packages[0], className);
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001741 info = mPackageManager.getActivityInfo(cn, 0);
Jason Monk41314972014-03-03 16:11:30 -05001742 }
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001743 final Intent intent = buildMainIntent();
Jason Monk41314972014-03-03 16:11:30 -05001744 intent.setComponent(cn);
1745 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
1746 Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
1747
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001748 return addAppShortcut(db, values, info.loadLabel(mPackageManager).toString(),
Jason Monk41314972014-03-03 16:11:30 -05001749 intent);
1750 } catch (PackageManager.NameNotFoundException e) {
1751 Log.w(TAG, "Unable to add favorite: " + packageName +
1752 "/" + className, e);
1753 }
1754 return -1;
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001755 } else if (!TextUtils.isEmpty(uri)) {
Jason Monk41314972014-03-03 16:11:30 -05001756 // If no component specified try to find a shortcut to add from the URI.
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001757 return addAppShortcutByUri(db, values, uri);
Jason Monk41314972014-03-03 16:11:30 -05001758 } else {
1759 Log.e(TAG, "Skipping invalid <favorite> with no component or uri");
1760 return -1;
1761 }
1762 }
1763
1764 private long addAppShortcut(SQLiteDatabase db, ContentValues values, String title,
1765 Intent intent) {
1766 long id = generateNewItemId();
1767 values.put(Favorites.INTENT, intent.toUri(0));
1768 values.put(Favorites.TITLE, title);
1769 values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_APPLICATION);
1770 values.put(Favorites.SPANX, 1);
1771 values.put(Favorites.SPANY, 1);
1772 values.put(Favorites._ID, id);
1773 if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values) < 0) {
1774 return -1;
1775 } else {
1776 return id;
1777 }
Adam Cohen228da5a2011-07-27 22:23:47 -07001778 }
1779
1780 private long addFolder(SQLiteDatabase db, ContentValues values) {
1781 values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_FOLDER);
1782 values.put(Favorites.SPANX, 1);
1783 values.put(Favorites.SPANY, 1);
Adam Cohendcd297f2013-06-18 13:13:40 -07001784 long id = generateNewItemId();
Adam Cohen228da5a2011-07-27 22:23:47 -07001785 values.put(Favorites._ID, id);
1786 if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values) <= 0) {
1787 return -1;
1788 } else {
1789 return id;
1790 }
The Android Open Source Projectf96811c2009-03-18 17:39:48 -07001791 }
1792
Bjorn Bringertcd8fec02010-01-14 13:26:43 +00001793 private ComponentName getSearchWidgetProvider() {
1794 SearchManager searchManager =
1795 (SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE);
1796 ComponentName searchComponent = searchManager.getGlobalSearchActivity();
1797 if (searchComponent == null) return null;
1798 return getProviderInPackage(searchComponent.getPackageName());
1799 }
1800
1801 /**
1802 * Gets an appwidget provider from the given package. If the package contains more than
1803 * one appwidget provider, an arbitrary one is returned.
1804 */
1805 private ComponentName getProviderInPackage(String packageName) {
1806 AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext);
1807 List<AppWidgetProviderInfo> providers = appWidgetManager.getInstalledProviders();
1808 if (providers == null) return null;
1809 final int providerCount = providers.size();
1810 for (int i = 0; i < providerCount; i++) {
1811 ComponentName provider = providers.get(i).provider;
1812 if (provider != null && provider.getPackageName().equals(packageName)) {
1813 return provider;
1814 }
1815 }
1816 return null;
1817 }
1818
Adam Cohen9b8f51f2014-05-30 15:34:09 -07001819 private boolean addAppWidget(XmlResourceParser parser, int type,
1820 SQLiteDatabase db, ContentValues values)
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001821 throws XmlPullParserException, IOException {
Romain Guy693599f2010-03-23 10:58:18 -07001822
Adam Cohen9b8f51f2014-05-30 15:34:09 -07001823 String packageName = getAttributeValue(parser, ATTR_PACKAGE_NAME);
1824 String className = getAttributeValue(parser, ATTR_CLASS_NAME);
Mike Cleronb87bd162009-10-30 16:36:56 -07001825
1826 if (packageName == null || className == null) {
1827 return false;
1828 }
Romain Guy693599f2010-03-23 10:58:18 -07001829
1830 boolean hasPackage = true;
Mike Cleronb87bd162009-10-30 16:36:56 -07001831 ComponentName cn = new ComponentName(packageName, className);
Romain Guy693599f2010-03-23 10:58:18 -07001832 try {
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001833 mPackageManager.getReceiverInfo(cn, 0);
Romain Guy693599f2010-03-23 10:58:18 -07001834 } catch (Exception e) {
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001835 String[] packages = mPackageManager.currentToCanonicalPackageNames(
Romain Guy693599f2010-03-23 10:58:18 -07001836 new String[] { packageName });
1837 cn = new ComponentName(packages[0], className);
1838 try {
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001839 mPackageManager.getReceiverInfo(cn, 0);
Romain Guy693599f2010-03-23 10:58:18 -07001840 } catch (Exception e1) {
Adam Cohen9b8f51f2014-05-30 15:34:09 -07001841 System.out.println("Can't find widget provider: " + className);
Romain Guy693599f2010-03-23 10:58:18 -07001842 hasPackage = false;
1843 }
1844 }
1845
1846 if (hasPackage) {
Adam Cohen9b8f51f2014-05-30 15:34:09 -07001847 String spanX = getAttributeValue(parser, ATTR_SPAN_X);
1848 String spanY = getAttributeValue(parser, ATTR_SPAN_Y);
1849
1850 values.put(Favorites.SPANX, spanX);
1851 values.put(Favorites.SPANY, spanY);
Winson Chungb3302ae2012-05-01 10:19:14 -07001852
1853 // Read the extras
1854 Bundle extras = new Bundle();
1855 int widgetDepth = parser.getDepth();
1856 while ((type = parser.next()) != XmlPullParser.END_TAG ||
1857 parser.getDepth() > widgetDepth) {
1858 if (type != XmlPullParser.START_TAG) {
1859 continue;
1860 }
1861
Winson Chungb3302ae2012-05-01 10:19:14 -07001862 if (TAG_EXTRA.equals(parser.getName())) {
Adam Cohen9b8f51f2014-05-30 15:34:09 -07001863 String key = getAttributeValue(parser, ATTR_KEY);
1864 String value = getAttributeValue(parser, ATTR_VALUE);
Winson Chungb3302ae2012-05-01 10:19:14 -07001865 if (key != null && value != null) {
1866 extras.putString(key, value);
1867 } else {
1868 throw new RuntimeException("Widget extras must have a key and value");
1869 }
1870 } else {
1871 throw new RuntimeException("Widgets can contain only extras");
1872 }
Winson Chungb3302ae2012-05-01 10:19:14 -07001873 }
1874
Adam Cohen9b8f51f2014-05-30 15:34:09 -07001875 return addAppWidget(db, values, cn, extras);
Romain Guy693599f2010-03-23 10:58:18 -07001876 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -07001877
Romain Guy693599f2010-03-23 10:58:18 -07001878 return false;
Bjorn Bringert7984c942009-12-09 15:38:25 +00001879 }
Adam Cohena043fa82014-07-23 14:49:38 -07001880
Bjorn Bringert7984c942009-12-09 15:38:25 +00001881 private boolean addAppWidget(SQLiteDatabase db, ContentValues values, ComponentName cn,
Adam Cohen9b8f51f2014-05-30 15:34:09 -07001882 Bundle extras) {
Mike Cleronb87bd162009-10-30 16:36:56 -07001883 boolean allocatedAppWidgets = false;
1884 final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext);
1885
1886 try {
1887 int appWidgetId = mAppWidgetHost.allocateAppWidgetId();
Daniel Lehmannc3a80402012-04-23 21:35:11 -07001888
Mike Cleronb87bd162009-10-30 16:36:56 -07001889 values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_APPWIDGET);
Mike Cleronb87bd162009-10-30 16:36:56 -07001890 values.put(Favorites.APPWIDGET_ID, appWidgetId);
Chris Wrend5e66bf2013-09-16 14:02:29 -04001891 values.put(Favorites.APPWIDGET_PROVIDER, cn.flattenToString());
Adam Cohendcd297f2013-06-18 13:13:40 -07001892 values.put(Favorites._ID, generateNewItemId());
Michael Jurkaa8c760d2011-04-28 14:59:33 -07001893 dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values);
Mike Cleronb87bd162009-10-30 16:36:56 -07001894
1895 allocatedAppWidgets = true;
Daniel Lehmannc3a80402012-04-23 21:35:11 -07001896
Michael Jurka8b805b12012-04-18 14:23:14 -07001897 // TODO: need to check return value
1898 appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, cn);
Winson Chungb3302ae2012-05-01 10:19:14 -07001899
1900 // Send a broadcast to configure the widget
1901 if (extras != null && !extras.isEmpty()) {
1902 Intent intent = new Intent(ACTION_APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE);
1903 intent.setComponent(cn);
1904 intent.putExtras(extras);
1905 intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
1906 mContext.sendBroadcast(intent);
1907 }
Mike Cleronb87bd162009-10-30 16:36:56 -07001908 } catch (RuntimeException ex) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -08001909 Log.e(TAG, "Problem allocating appWidgetId", ex);
Mike Cleronb87bd162009-10-30 16:36:56 -07001910 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -07001911
Mike Cleronb87bd162009-10-30 16:36:56 -07001912 return allocatedAppWidgets;
1913 }
Adam Cohen228da5a2011-07-27 22:23:47 -07001914
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001915 private long addUriShortcut(SQLiteDatabase db, ContentValues values, Resources res,
1916 XmlResourceParser parser) {
1917 final int iconResId = getAttributeResourceValue(parser, ATTR_ICON, 0);
1918 final int titleResId = getAttributeResourceValue(parser, ATTR_TITLE, 0);
Mike Cleronb87bd162009-10-30 16:36:56 -07001919
Romain Guy7eb9e5e2009-12-02 20:10:07 -08001920 Intent intent;
Mike Cleronb87bd162009-10-30 16:36:56 -07001921 String uri = null;
1922 try {
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001923 uri = getAttributeValue(parser, ATTR_URI);
Mike Cleronb87bd162009-10-30 16:36:56 -07001924 intent = Intent.parseUri(uri, 0);
1925 } catch (URISyntaxException e) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -08001926 Log.w(TAG, "Shortcut has malformed uri: " + uri);
Adam Cohen228da5a2011-07-27 22:23:47 -07001927 return -1; // Oh well
Mike Cleronb87bd162009-10-30 16:36:56 -07001928 }
1929
1930 if (iconResId == 0 || titleResId == 0) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -08001931 Log.w(TAG, "Shortcut is missing title or icon resource ID");
Adam Cohen228da5a2011-07-27 22:23:47 -07001932 return -1;
Mike Cleronb87bd162009-10-30 16:36:56 -07001933 }
1934
Adam Cohendcd297f2013-06-18 13:13:40 -07001935 long id = generateNewItemId();
Mike Cleronb87bd162009-10-30 16:36:56 -07001936 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1937 values.put(Favorites.INTENT, intent.toUri(0));
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001938 values.put(Favorites.TITLE, res.getString(titleResId));
Mike Cleronb87bd162009-10-30 16:36:56 -07001939 values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_SHORTCUT);
1940 values.put(Favorites.SPANX, 1);
1941 values.put(Favorites.SPANY, 1);
1942 values.put(Favorites.ICON_TYPE, Favorites.ICON_TYPE_RESOURCE);
Jeff Sharkey5aeef582014-04-14 13:26:42 -07001943 values.put(Favorites.ICON_PACKAGE, res.getResourcePackageName(iconResId));
1944 values.put(Favorites.ICON_RESOURCE, res.getResourceName(iconResId));
Adam Cohen228da5a2011-07-27 22:23:47 -07001945 values.put(Favorites._ID, id);
Mike Cleronb87bd162009-10-30 16:36:56 -07001946
Adam Cohen228da5a2011-07-27 22:23:47 -07001947 if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values) < 0) {
1948 return -1;
1949 }
1950 return id;
Mike Cleronb87bd162009-10-30 16:36:56 -07001951 }
Dan Sandlerd5024042014-01-09 15:01:33 -05001952
Sunny Goyal0fe505b2014-08-06 09:55:36 -07001953 private void migrateLauncher2Shortcuts(SQLiteDatabase db, Uri uri) {
Dan Sandlerd5024042014-01-09 15:01:33 -05001954 final ContentResolver resolver = mContext.getContentResolver();
1955 Cursor c = null;
1956 int count = 0;
1957 int curScreen = 0;
1958
1959 try {
1960 c = resolver.query(uri, null, null, null, "title ASC");
1961 } catch (Exception e) {
1962 // Ignore
1963 }
1964
Dan Sandlerd5024042014-01-09 15:01:33 -05001965 // We already have a favorites database in the old provider
1966 if (c != null) {
1967 try {
1968 if (c.getCount() > 0) {
1969 final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
1970 final int intentIndex
1971 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.INTENT);
1972 final int titleIndex
1973 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE);
1974 final int iconTypeIndex
1975 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_TYPE);
1976 final int iconIndex
1977 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON);
1978 final int iconPackageIndex
1979 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_PACKAGE);
1980 final int iconResourceIndex
1981 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_RESOURCE);
1982 final int containerIndex
1983 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER);
1984 final int itemTypeIndex
1985 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE);
1986 final int screenIndex
1987 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN);
1988 final int cellXIndex
1989 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX);
1990 final int cellYIndex
1991 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY);
1992 final int uriIndex
1993 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.URI);
1994 final int displayModeIndex
1995 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.DISPLAY_MODE);
Kenny Guyed131872014-04-30 03:02:21 +01001996 final int profileIndex
1997 = c.getColumnIndex(LauncherSettings.Favorites.PROFILE_ID);
Dan Sandlerd5024042014-01-09 15:01:33 -05001998
1999 int i = 0;
2000 int curX = 0;
2001 int curY = 0;
2002
2003 final LauncherAppState app = LauncherAppState.getInstance();
2004 final DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
2005 final int width = (int) grid.numColumns;
2006 final int height = (int) grid.numRows;
2007 final int hotseatWidth = (int) grid.numHotseatIcons;
2008
2009 final HashSet<String> seenIntents = new HashSet<String>(c.getCount());
2010
Adam Cohen72960972014-01-15 18:13:55 -08002011 final ArrayList<ContentValues> shortcuts = new ArrayList<ContentValues>();
2012 final ArrayList<ContentValues> folders = new ArrayList<ContentValues>();
Dan Sandlerab5fa3a2014-03-06 23:48:04 -05002013 final SparseArray<ContentValues> hotseat = new SparseArray<ContentValues>();
Dan Sandlerd5024042014-01-09 15:01:33 -05002014
2015 while (c.moveToNext()) {
2016 final int itemType = c.getInt(itemTypeIndex);
2017 if (itemType != Favorites.ITEM_TYPE_APPLICATION
2018 && itemType != Favorites.ITEM_TYPE_SHORTCUT
2019 && itemType != Favorites.ITEM_TYPE_FOLDER) {
2020 continue;
2021 }
2022
2023 final int cellX = c.getInt(cellXIndex);
2024 final int cellY = c.getInt(cellYIndex);
2025 final int screen = c.getInt(screenIndex);
2026 int container = c.getInt(containerIndex);
2027 final String intentStr = c.getString(intentIndex);
Kenny Guyed131872014-04-30 03:02:21 +01002028
2029 UserManagerCompat userManager = UserManagerCompat.getInstance(mContext);
2030 UserHandleCompat userHandle;
2031 final long userSerialNumber;
2032 if (profileIndex != -1 && !c.isNull(profileIndex)) {
2033 userSerialNumber = c.getInt(profileIndex);
2034 userHandle = userManager.getUserForSerialNumber(userSerialNumber);
2035 } else {
2036 // Default to the serial number of this user, for older
2037 // shortcuts.
2038 userHandle = UserHandleCompat.myUserHandle();
2039 userSerialNumber = userManager.getSerialNumberForUser(userHandle);
2040 }
Dan Sandlerd5024042014-01-09 15:01:33 -05002041 Launcher.addDumpLog(TAG, "migrating \""
Dan Sandlerab5fa3a2014-03-06 23:48:04 -05002042 + c.getString(titleIndex) + "\" ("
2043 + cellX + "," + cellY + "@"
2044 + LauncherSettings.Favorites.containerToString(container)
2045 + "/" + screen
2046 + "): " + intentStr, true);
Dan Sandlerd5024042014-01-09 15:01:33 -05002047
2048 if (itemType != Favorites.ITEM_TYPE_FOLDER) {
Adam Cohen556f6132014-01-15 15:18:08 -08002049
2050 final Intent intent;
2051 final ComponentName cn;
2052 try {
2053 intent = Intent.parseUri(intentStr, 0);
2054 } catch (URISyntaxException e) {
2055 // bogus intent?
2056 Launcher.addDumpLog(TAG,
2057 "skipping invalid intent uri", true);
2058 continue;
2059 }
2060
2061 cn = intent.getComponent();
Dan Sandlerd5024042014-01-09 15:01:33 -05002062 if (TextUtils.isEmpty(intentStr)) {
2063 // no intent? no icon
2064 Launcher.addDumpLog(TAG, "skipping empty intent", true);
2065 continue;
Adam Cohen72960972014-01-15 18:13:55 -08002066 } else if (cn != null &&
Kenny Guyed131872014-04-30 03:02:21 +01002067 !LauncherModel.isValidPackageActivity(mContext, cn,
2068 userHandle)) {
Adam Cohen556f6132014-01-15 15:18:08 -08002069 // component no longer exists.
Adam Cohen72960972014-01-15 18:13:55 -08002070 Launcher.addDumpLog(TAG, "skipping item whose component " +
Adam Cohen556f6132014-01-15 15:18:08 -08002071 "no longer exists.", true);
2072 continue;
Adam Cohen72960972014-01-15 18:13:55 -08002073 } else if (container ==
2074 LauncherSettings.Favorites.CONTAINER_DESKTOP) {
2075 // Dedupe icons directly on the workspace
2076
Adam Cohen556f6132014-01-15 15:18:08 -08002077 // Canonicalize
2078 // the Play Store sets the package parameter, but Launcher
2079 // does not, so we clear that out to keep them the same
2080 intent.setPackage(null);
2081 final String key = intent.toUri(0);
2082 if (seenIntents.contains(key)) {
2083 Launcher.addDumpLog(TAG, "skipping duplicate", true);
Dan Sandlerd5024042014-01-09 15:01:33 -05002084 continue;
Adam Cohen556f6132014-01-15 15:18:08 -08002085 } else {
2086 seenIntents.add(key);
Dan Sandlerd5024042014-01-09 15:01:33 -05002087 }
2088 }
2089 }
2090
2091 ContentValues values = new ContentValues(c.getColumnCount());
2092 values.put(LauncherSettings.Favorites._ID, c.getInt(idIndex));
2093 values.put(LauncherSettings.Favorites.INTENT, intentStr);
2094 values.put(LauncherSettings.Favorites.TITLE, c.getString(titleIndex));
2095 values.put(LauncherSettings.Favorites.ICON_TYPE,
2096 c.getInt(iconTypeIndex));
2097 values.put(LauncherSettings.Favorites.ICON, c.getBlob(iconIndex));
2098 values.put(LauncherSettings.Favorites.ICON_PACKAGE,
2099 c.getString(iconPackageIndex));
2100 values.put(LauncherSettings.Favorites.ICON_RESOURCE,
2101 c.getString(iconResourceIndex));
2102 values.put(LauncherSettings.Favorites.ITEM_TYPE, itemType);
2103 values.put(LauncherSettings.Favorites.APPWIDGET_ID, -1);
2104 values.put(LauncherSettings.Favorites.URI, c.getString(uriIndex));
2105 values.put(LauncherSettings.Favorites.DISPLAY_MODE,
2106 c.getInt(displayModeIndex));
Kenny Guyed131872014-04-30 03:02:21 +01002107 values.put(LauncherSettings.Favorites.PROFILE_ID, userSerialNumber);
Dan Sandlerd5024042014-01-09 15:01:33 -05002108
Dan Sandlerab5fa3a2014-03-06 23:48:04 -05002109 if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
2110 hotseat.put(screen, values);
Dan Sandlerd5024042014-01-09 15:01:33 -05002111 }
2112
2113 if (container != LauncherSettings.Favorites.CONTAINER_DESKTOP) {
2114 // In a folder or in the hotseat, preserve position
2115 values.put(LauncherSettings.Favorites.SCREEN, screen);
2116 values.put(LauncherSettings.Favorites.CELLX, cellX);
2117 values.put(LauncherSettings.Favorites.CELLY, cellY);
2118 } else {
Adam Cohen72960972014-01-15 18:13:55 -08002119 // For items contained directly on one of the workspace screen,
2120 // we'll determine their location (screen, x, y) in a second pass.
Dan Sandlerd5024042014-01-09 15:01:33 -05002121 }
2122
2123 values.put(LauncherSettings.Favorites.CONTAINER, container);
2124
Adam Cohen72960972014-01-15 18:13:55 -08002125 if (itemType != Favorites.ITEM_TYPE_FOLDER) {
2126 shortcuts.add(values);
2127 } else {
2128 folders.add(values);
2129 }
Dan Sandlerd5024042014-01-09 15:01:33 -05002130 }
2131
Dan Sandlerab5fa3a2014-03-06 23:48:04 -05002132 // Now that we have all the hotseat icons, let's go through them left-right
2133 // and assign valid locations for them in the new hotseat
2134 final int N = hotseat.size();
2135 for (int idx=0; idx<N; idx++) {
2136 int hotseatX = hotseat.keyAt(idx);
2137 ContentValues values = hotseat.valueAt(idx);
2138
2139 if (hotseatX == grid.hotseatAllAppsRank) {
2140 // let's drop this in the next available hole in the hotseat
2141 while (++hotseatX < hotseatWidth) {
2142 if (hotseat.get(hotseatX) == null) {
2143 // found a spot! move it here
2144 values.put(LauncherSettings.Favorites.SCREEN,
2145 hotseatX);
2146 break;
2147 }
2148 }
2149 }
2150 if (hotseatX >= hotseatWidth) {
2151 // no room for you in the hotseat? it's off to the desktop with you
2152 values.put(LauncherSettings.Favorites.CONTAINER,
2153 Favorites.CONTAINER_DESKTOP);
2154 }
2155 }
2156
Adam Cohen72960972014-01-15 18:13:55 -08002157 final ArrayList<ContentValues> allItems = new ArrayList<ContentValues>();
2158 // Folders first
2159 allItems.addAll(folders);
2160 // Then shortcuts
2161 allItems.addAll(shortcuts);
2162
2163 // Layout all the folders
2164 for (ContentValues values: allItems) {
2165 if (values.getAsInteger(LauncherSettings.Favorites.CONTAINER) !=
2166 LauncherSettings.Favorites.CONTAINER_DESKTOP) {
2167 // Hotseat items and folder items have already had their
2168 // location information set. Nothing to be done here.
2169 continue;
2170 }
2171 values.put(LauncherSettings.Favorites.SCREEN, curScreen);
2172 values.put(LauncherSettings.Favorites.CELLX, curX);
2173 values.put(LauncherSettings.Favorites.CELLY, curY);
2174 curX = (curX + 1) % width;
2175 if (curX == 0) {
2176 curY = (curY + 1);
2177 }
2178 // Leave the last row of icons blank on every screen
2179 if (curY == height - 1) {
2180 curScreen = (int) generateNewScreenId();
2181 curY = 0;
2182 }
2183 }
2184
2185 if (allItems.size() > 0) {
Dan Sandlerd5024042014-01-09 15:01:33 -05002186 db.beginTransaction();
2187 try {
Adam Cohen72960972014-01-15 18:13:55 -08002188 for (ContentValues row: allItems) {
2189 if (row == null) continue;
2190 if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, row)
Dan Sandlerd5024042014-01-09 15:01:33 -05002191 < 0) {
2192 return;
2193 } else {
2194 count++;
2195 }
2196 }
2197 db.setTransactionSuccessful();
2198 } finally {
2199 db.endTransaction();
2200 }
2201 }
2202
2203 db.beginTransaction();
2204 try {
2205 for (i=0; i<=curScreen; i++) {
2206 final ContentValues values = new ContentValues();
2207 values.put(LauncherSettings.WorkspaceScreens._ID, i);
2208 values.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, i);
2209 if (dbInsertAndCheck(this, db, TABLE_WORKSPACE_SCREENS, null, values)
2210 < 0) {
2211 return;
2212 }
2213 }
2214 db.setTransactionSuccessful();
2215 } finally {
2216 db.endTransaction();
2217 }
2218 }
2219 } finally {
2220 c.close();
2221 }
2222 }
2223
2224 Launcher.addDumpLog(TAG, "migrated " + count + " icons from Launcher2 into "
2225 + (curScreen+1) + " screens", true);
2226
2227 // ensure that new screens are created to hold these icons
2228 setFlagJustLoadedOldDb();
2229
2230 // Update max IDs; very important since we just grabbed IDs from another database
2231 mMaxItemId = initializeMaxItemId(db);
2232 mMaxScreenId = initializeMaxScreenId(db);
2233 if (LOGD) Log.d(TAG, "mMaxItemId: " + mMaxItemId + " mMaxScreenId: " + mMaxScreenId);
2234 }
Mike Cleronb87bd162009-10-30 16:36:56 -07002235 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -07002236
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002237 /**
2238 * Build a query string that will match any row where the column matches
2239 * anything in the values list.
2240 */
Sunny Goyal0fe505b2014-08-06 09:55:36 -07002241 private static String buildOrWhereString(String column, int[] values) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002242 StringBuilder selectWhere = new StringBuilder();
2243 for (int i = values.length - 1; i >= 0; i--) {
2244 selectWhere.append(column).append("=").append(values[i]);
2245 if (i > 0) {
2246 selectWhere.append(" OR ");
2247 }
2248 }
2249 return selectWhere.toString();
2250 }
2251
Jeff Sharkey5aeef582014-04-14 13:26:42 -07002252 /**
2253 * Return attribute value, attempting launcher-specific namespace first
2254 * before falling back to anonymous attribute.
2255 */
Sunny Goyal0fe505b2014-08-06 09:55:36 -07002256 private static String getAttributeValue(XmlResourceParser parser, String attribute) {
Jeff Sharkey5aeef582014-04-14 13:26:42 -07002257 String value = parser.getAttributeValue(
2258 "http://schemas.android.com/apk/res-auto/com.android.launcher3", attribute);
2259 if (value == null) {
2260 value = parser.getAttributeValue(null, attribute);
2261 }
2262 return value;
2263 }
2264
2265 /**
2266 * Return attribute resource value, attempting launcher-specific namespace
2267 * first before falling back to anonymous attribute.
2268 */
Sunny Goyal0fe505b2014-08-06 09:55:36 -07002269 private static int getAttributeResourceValue(XmlResourceParser parser, String attribute,
Jeff Sharkey5aeef582014-04-14 13:26:42 -07002270 int defaultValue) {
2271 int value = parser.getAttributeResourceValue(
2272 "http://schemas.android.com/apk/res-auto/com.android.launcher3", attribute,
2273 defaultValue);
2274 if (value == defaultValue) {
2275 value = parser.getAttributeResourceValue(null, attribute, defaultValue);
2276 }
2277 return value;
2278 }
2279
2280 private static void copyInteger(ContentValues from, ContentValues to, String key) {
2281 to.put(key, from.getAsInteger(key));
2282 }
2283
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002284 static class SqlArguments {
2285 public final String table;
2286 public final String where;
2287 public final String[] args;
2288
2289 SqlArguments(Uri url, String where, String[] args) {
2290 if (url.getPathSegments().size() == 1) {
2291 this.table = url.getPathSegments().get(0);
2292 this.where = where;
2293 this.args = args;
2294 } else if (url.getPathSegments().size() != 2) {
2295 throw new IllegalArgumentException("Invalid URI: " + url);
2296 } else if (!TextUtils.isEmpty(where)) {
2297 throw new UnsupportedOperationException("WHERE clause not supported: " + url);
2298 } else {
2299 this.table = url.getPathSegments().get(0);
Daniel Lehmannc3a80402012-04-23 21:35:11 -07002300 this.where = "_id=" + ContentUris.parseId(url);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002301 this.args = null;
2302 }
2303 }
2304
2305 SqlArguments(Uri url) {
2306 if (url.getPathSegments().size() == 1) {
2307 table = url.getPathSegments().get(0);
2308 where = null;
2309 args = null;
2310 } else {
2311 throw new IllegalArgumentException("Invalid URI: " + url);
2312 }
2313 }
2314 }
Sunny Goyal0fe505b2014-08-06 09:55:36 -07002315
2316 static interface WorkspaceLoader {
2317 /**
2318 * @param screenIds A mutable list of screen its
2319 * @return the number of workspace items added.
2320 */
2321 int loadLayout(SQLiteDatabase db, ArrayList<Long> screenIds);
2322 }
2323
2324 private static class SimpleWorkspaceLoader implements WorkspaceLoader {
2325 private final Resources mRes;
2326 private final int mWorkspaceId;
2327 private final DatabaseHelper mHelper;
2328
2329 SimpleWorkspaceLoader(DatabaseHelper helper, Resources res, int workspaceId) {
2330 mHelper = helper;
2331 mRes = res;
2332 mWorkspaceId = workspaceId;
2333 }
2334
2335 @Override
2336 public int loadLayout(SQLiteDatabase db, ArrayList<Long> screenIds) {
2337 return mHelper.loadFavoritesRecursive(db, mRes, mWorkspaceId, screenIds);
2338 }
2339 }
Adam Cohen72960972014-01-15 18:13:55 -08002340}