LauncherProvider.java revision 383c507c47a5abf3258fc04220f37c366f983a97
1/* 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 17package com.android.launcher3; 18 19import android.annotation.TargetApi; 20import android.appwidget.AppWidgetHost; 21import android.appwidget.AppWidgetManager; 22import android.content.ComponentName; 23import android.content.ContentProvider; 24import android.content.ContentProviderOperation; 25import android.content.ContentProviderResult; 26import android.content.ContentResolver; 27import android.content.ContentUris; 28import android.content.ContentValues; 29import android.content.Context; 30import android.content.Intent; 31import android.content.OperationApplicationException; 32import android.content.SharedPreferences; 33import android.content.pm.PackageManager.NameNotFoundException; 34import android.content.res.Resources; 35import android.database.Cursor; 36import android.database.SQLException; 37import android.database.sqlite.SQLiteDatabase; 38import android.database.sqlite.SQLiteOpenHelper; 39import android.database.sqlite.SQLiteQueryBuilder; 40import android.database.sqlite.SQLiteStatement; 41import android.net.Uri; 42import android.os.Binder; 43import android.os.Build; 44import android.os.Bundle; 45import android.os.Process; 46import android.os.StrictMode; 47import android.os.UserManager; 48import android.text.TextUtils; 49import android.util.Log; 50import android.util.SparseArray; 51 52import com.android.launcher3.AutoInstallsLayout.LayoutParserCallback; 53import com.android.launcher3.LauncherSettings.Favorites; 54import com.android.launcher3.compat.UserHandleCompat; 55import com.android.launcher3.compat.UserManagerCompat; 56import com.android.launcher3.config.ProviderConfig; 57import com.android.launcher3.util.ManagedProfileHeuristic; 58import com.android.launcher3.util.Thunk; 59 60import java.io.File; 61import java.net.URISyntaxException; 62import java.util.ArrayList; 63import java.util.Collections; 64import java.util.HashSet; 65 66public class LauncherProvider extends ContentProvider { 67 private static final String TAG = "Launcher.LauncherProvider"; 68 private static final boolean LOGD = false; 69 70 private static final int DATABASE_VERSION = 26; 71 72 static final String OLD_AUTHORITY = "com.android.launcher2.settings"; 73 static final String AUTHORITY = ProviderConfig.AUTHORITY; 74 75 static final String TABLE_FAVORITES = LauncherSettings.Favorites.TABLE_NAME; 76 static final String TABLE_WORKSPACE_SCREENS = LauncherSettings.WorkspaceScreens.TABLE_NAME; 77 static final String EMPTY_DATABASE_CREATED = "EMPTY_DATABASE_CREATED"; 78 79 private static final String URI_PARAM_IS_EXTERNAL_ADD = "isExternalAdd"; 80 81 private static final String RESTRICTION_PACKAGE_NAME = "workspace.configuration.package.name"; 82 83 @Thunk LauncherProviderChangeListener mListener; 84 @Thunk DatabaseHelper mOpenHelper; 85 86 @Override 87 public boolean onCreate() { 88 final Context context = getContext(); 89 StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites(); 90 mOpenHelper = new DatabaseHelper(context); 91 StrictMode.setThreadPolicy(oldPolicy); 92 LauncherAppState.setLauncherProvider(this); 93 return true; 94 } 95 96 public boolean wasNewDbCreated() { 97 return mOpenHelper.wasNewDbCreated(); 98 } 99 100 public void setLauncherProviderChangeListener(LauncherProviderChangeListener listener) { 101 mListener = listener; 102 } 103 104 @Override 105 public String getType(Uri uri) { 106 SqlArguments args = new SqlArguments(uri, null, null); 107 if (TextUtils.isEmpty(args.where)) { 108 return "vnd.android.cursor.dir/" + args.table; 109 } else { 110 return "vnd.android.cursor.item/" + args.table; 111 } 112 } 113 114 @Override 115 public Cursor query(Uri uri, String[] projection, String selection, 116 String[] selectionArgs, String sortOrder) { 117 118 SqlArguments args = new SqlArguments(uri, selection, selectionArgs); 119 SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); 120 qb.setTables(args.table); 121 122 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 123 Cursor result = qb.query(db, projection, args.where, args.args, null, null, sortOrder); 124 result.setNotificationUri(getContext().getContentResolver(), uri); 125 126 return result; 127 } 128 129 @Thunk static long dbInsertAndCheck(DatabaseHelper helper, 130 SQLiteDatabase db, String table, String nullColumnHack, ContentValues values) { 131 if (values == null) { 132 throw new RuntimeException("Error: attempting to insert null values"); 133 } 134 if (!values.containsKey(LauncherSettings.ChangeLogColumns._ID)) { 135 throw new RuntimeException("Error: attempting to add item without specifying an id"); 136 } 137 helper.checkId(table, values); 138 return db.insert(table, nullColumnHack, values); 139 } 140 141 @Override 142 public Uri insert(Uri uri, ContentValues initialValues) { 143 SqlArguments args = new SqlArguments(uri); 144 145 // In very limited cases, we support system|signature permission apps to add to the db 146 String externalAdd = uri.getQueryParameter(URI_PARAM_IS_EXTERNAL_ADD); 147 final boolean isExternalAll = externalAdd != null && "true".equals(externalAdd); 148 if (isExternalAll) { 149 if (!mOpenHelper.initializeExternalAdd(initialValues)) { 150 return null; 151 } 152 } 153 154 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 155 addModifiedTime(initialValues); 156 final long rowId = dbInsertAndCheck(mOpenHelper, db, args.table, null, initialValues); 157 if (rowId < 0) return null; 158 159 uri = ContentUris.withAppendedId(uri, rowId); 160 notifyListeners(); 161 162 if (isExternalAll) { 163 LauncherAppState app = LauncherAppState.getInstanceNoCreate(); 164 if (app != null) { 165 app.reloadWorkspace(); 166 } 167 } 168 169 return uri; 170 } 171 172 173 @Override 174 public int bulkInsert(Uri uri, ContentValues[] values) { 175 SqlArguments args = new SqlArguments(uri); 176 177 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 178 db.beginTransaction(); 179 try { 180 int numValues = values.length; 181 for (int i = 0; i < numValues; i++) { 182 addModifiedTime(values[i]); 183 if (dbInsertAndCheck(mOpenHelper, db, args.table, null, values[i]) < 0) { 184 return 0; 185 } 186 } 187 db.setTransactionSuccessful(); 188 } finally { 189 db.endTransaction(); 190 } 191 192 notifyListeners(); 193 return values.length; 194 } 195 196 @Override 197 public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations) 198 throws OperationApplicationException { 199 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 200 db.beginTransaction(); 201 try { 202 ContentProviderResult[] result = super.applyBatch(operations); 203 db.setTransactionSuccessful(); 204 return result; 205 } finally { 206 db.endTransaction(); 207 } 208 } 209 210 @Override 211 public int delete(Uri uri, String selection, String[] selectionArgs) { 212 SqlArguments args = new SqlArguments(uri, selection, selectionArgs); 213 214 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 215 int count = db.delete(args.table, args.where, args.args); 216 if (count > 0) notifyListeners(); 217 218 return count; 219 } 220 221 @Override 222 public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { 223 SqlArguments args = new SqlArguments(uri, selection, selectionArgs); 224 225 addModifiedTime(values); 226 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 227 int count = db.update(args.table, values, args.where, args.args); 228 if (count > 0) notifyListeners(); 229 230 return count; 231 } 232 233 @Override 234 public Bundle call(String method, String arg, Bundle extras) { 235 if (Binder.getCallingUid() != Process.myUid()) { 236 return null; 237 } 238 239 switch (method) { 240 case LauncherSettings.Settings.METHOD_GET_BOOLEAN: { 241 Bundle result = new Bundle(); 242 result.putBoolean(LauncherSettings.Settings.EXTRA_VALUE, 243 getContext().getSharedPreferences( 244 LauncherAppState.getSharedPreferencesKey(), Context.MODE_PRIVATE) 245 .getBoolean(arg, extras.getBoolean( 246 LauncherSettings.Settings.EXTRA_DEFAULT_VALUE))); 247 return result; 248 } 249 case LauncherSettings.Settings.METHOD_SET_BOOLEAN: { 250 boolean value = extras.getBoolean(LauncherSettings.Settings.EXTRA_VALUE); 251 getContext().getSharedPreferences( 252 LauncherAppState.getSharedPreferencesKey(), Context.MODE_PRIVATE) 253 .edit().putBoolean(arg, value).apply(); 254 if (mListener != null) { 255 mListener.onSettingsChanged(arg, value); 256 } 257 Bundle result = new Bundle(); 258 result.putBoolean(LauncherSettings.Settings.EXTRA_VALUE, value); 259 return result; 260 } 261 } 262 return null; 263 } 264 265 private void notifyListeners() { 266 // always notify the backup agent 267 LauncherBackupAgentHelper.dataChanged(getContext()); 268 if (mListener != null) { 269 mListener.onLauncherProviderChange(); 270 } 271 } 272 273 @Thunk void notifyAppWidgetHostReset() { 274 new MainThreadExecutor().execute(new Runnable() { 275 276 @Override 277 public void run() { 278 if (mListener != null) { 279 mListener.onAppWidgetHostReset(); 280 } 281 } 282 }); 283 } 284 285 @Thunk static void addModifiedTime(ContentValues values) { 286 values.put(LauncherSettings.ChangeLogColumns.MODIFIED, System.currentTimeMillis()); 287 } 288 289 public long generateNewItemId() { 290 return mOpenHelper.generateNewItemId(); 291 } 292 293 public void updateMaxItemId(long id) { 294 mOpenHelper.updateMaxItemId(id); 295 } 296 297 public long generateNewScreenId() { 298 return mOpenHelper.generateNewScreenId(); 299 } 300 301 /** 302 * Clears all the data for a fresh start. 303 */ 304 synchronized public void createEmptyDB() { 305 mOpenHelper.createEmptyDB(mOpenHelper.getWritableDatabase()); 306 } 307 308 public void clearFlagEmptyDbCreated() { 309 String spKey = LauncherAppState.getSharedPreferencesKey(); 310 getContext().getSharedPreferences(spKey, Context.MODE_PRIVATE) 311 .edit() 312 .remove(EMPTY_DATABASE_CREATED) 313 .commit(); 314 } 315 316 /** 317 * Loads the default workspace based on the following priority scheme: 318 * 1) From the app restrictions 319 * 2) From a package provided by play store 320 * 3) From a partner configuration APK, already in the system image 321 * 4) The default configuration for the particular device 322 */ 323 synchronized public void loadDefaultFavoritesIfNecessary() { 324 String spKey = LauncherAppState.getSharedPreferencesKey(); 325 SharedPreferences sp = getContext().getSharedPreferences(spKey, Context.MODE_PRIVATE); 326 327 if (sp.getBoolean(EMPTY_DATABASE_CREATED, false)) { 328 Log.d(TAG, "loading default workspace"); 329 330 AutoInstallsLayout loader = createWorkspaceLoaderFromAppRestriction(); 331 if (loader == null) { 332 loader = AutoInstallsLayout.get(getContext(), 333 mOpenHelper.mAppWidgetHost, mOpenHelper); 334 } 335 if (loader == null) { 336 final Partner partner = Partner.get(getContext().getPackageManager()); 337 if (partner != null && partner.hasDefaultLayout()) { 338 final Resources partnerRes = partner.getResources(); 339 int workspaceResId = partnerRes.getIdentifier(Partner.RES_DEFAULT_LAYOUT, 340 "xml", partner.getPackageName()); 341 if (workspaceResId != 0) { 342 loader = new DefaultLayoutParser(getContext(), mOpenHelper.mAppWidgetHost, 343 mOpenHelper, partnerRes, workspaceResId); 344 } 345 } 346 } 347 348 final boolean usingExternallyProvidedLayout = loader != null; 349 if (loader == null) { 350 loader = getDefaultLayoutParser(); 351 } 352 353 // There might be some partially restored DB items, due to buggy restore logic in 354 // previous versions of launcher. 355 createEmptyDB(); 356 // Populate favorites table with initial favorites 357 if ((mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(), loader) <= 0) 358 && usingExternallyProvidedLayout) { 359 // Unable to load external layout. Cleanup and load the internal layout. 360 createEmptyDB(); 361 mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(), 362 getDefaultLayoutParser()); 363 } 364 clearFlagEmptyDbCreated(); 365 } 366 } 367 368 /** 369 * Creates workspace loader from an XML resource listed in the app restrictions. 370 * 371 * @return the loader if the restrictions are set and the resource exists; null otherwise. 372 */ 373 @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2) 374 private AutoInstallsLayout createWorkspaceLoaderFromAppRestriction() { 375 // UserManager.getApplicationRestrictions() requires minSdkVersion >= 18 376 if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) { 377 return null; 378 } 379 380 Context ctx = getContext(); 381 UserManager um = (UserManager) ctx.getSystemService(Context.USER_SERVICE); 382 Bundle bundle = um.getApplicationRestrictions(ctx.getPackageName()); 383 if (bundle == null) { 384 return null; 385 } 386 387 String packageName = bundle.getString(RESTRICTION_PACKAGE_NAME); 388 if (packageName != null) { 389 try { 390 Resources targetResources = ctx.getPackageManager() 391 .getResourcesForApplication(packageName); 392 return AutoInstallsLayout.get(ctx, packageName, targetResources, 393 mOpenHelper.mAppWidgetHost, mOpenHelper); 394 } catch (NameNotFoundException e) { 395 Log.e(TAG, "Target package for restricted profile not found", e); 396 return null; 397 } 398 } 399 return null; 400 } 401 402 private DefaultLayoutParser getDefaultLayoutParser() { 403 int defaultLayout = LauncherAppState.getInstance() 404 .getInvariantDeviceProfile().defaultLayoutId; 405 return new DefaultLayoutParser(getContext(), mOpenHelper.mAppWidgetHost, 406 mOpenHelper, getContext().getResources(), defaultLayout); 407 } 408 409 public void migrateLauncher2Shortcuts() { 410 mOpenHelper.migrateLauncher2Shortcuts(mOpenHelper.getWritableDatabase(), 411 Uri.parse(getContext().getString(R.string.old_launcher_provider_uri))); 412 } 413 414 public void updateFolderItemsRank() { 415 mOpenHelper.updateFolderItemsRank(mOpenHelper.getWritableDatabase(), false); 416 } 417 418 public void convertShortcutsToLauncherActivities() { 419 mOpenHelper.convertShortcutsToLauncherActivities(mOpenHelper.getWritableDatabase()); 420 } 421 422 423 public void deleteDatabase() { 424 // Are you sure? (y/n) 425 final SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 426 final File dbFile = new File(db.getPath()); 427 mOpenHelper.close(); 428 if (dbFile.exists()) { 429 SQLiteDatabase.deleteDatabase(dbFile); 430 } 431 mOpenHelper = new DatabaseHelper(getContext()); 432 } 433 434 private static class DatabaseHelper extends SQLiteOpenHelper implements LayoutParserCallback { 435 private final Context mContext; 436 @Thunk final AppWidgetHost mAppWidgetHost; 437 private long mMaxItemId = -1; 438 private long mMaxScreenId = -1; 439 440 private boolean mNewDbCreated = false; 441 442 DatabaseHelper(Context context) { 443 super(context, LauncherFiles.LAUNCHER_DB, null, DATABASE_VERSION); 444 mContext = context; 445 mAppWidgetHost = new AppWidgetHost(context, Launcher.APPWIDGET_HOST_ID); 446 447 // In the case where neither onCreate nor onUpgrade gets called, we read the maxId from 448 // the DB here 449 if (mMaxItemId == -1) { 450 mMaxItemId = initializeMaxItemId(getWritableDatabase()); 451 } 452 if (mMaxScreenId == -1) { 453 mMaxScreenId = initializeMaxScreenId(getWritableDatabase()); 454 } 455 } 456 457 public boolean wasNewDbCreated() { 458 return mNewDbCreated; 459 } 460 461 @Override 462 public void onCreate(SQLiteDatabase db) { 463 if (LOGD) Log.d(TAG, "creating new launcher database"); 464 465 mMaxItemId = 1; 466 mMaxScreenId = 0; 467 mNewDbCreated = true; 468 469 UserManagerCompat userManager = UserManagerCompat.getInstance(mContext); 470 long userSerialNumber = userManager.getSerialNumberForUser( 471 UserHandleCompat.myUserHandle()); 472 473 db.execSQL("CREATE TABLE favorites (" + 474 "_id INTEGER PRIMARY KEY," + 475 "title TEXT," + 476 "intent TEXT," + 477 "container INTEGER," + 478 "screen INTEGER," + 479 "cellX INTEGER," + 480 "cellY INTEGER," + 481 "spanX INTEGER," + 482 "spanY INTEGER," + 483 "itemType INTEGER," + 484 "appWidgetId INTEGER NOT NULL DEFAULT -1," + 485 "isShortcut INTEGER," + 486 "iconType INTEGER," + 487 "iconPackage TEXT," + 488 "iconResource TEXT," + 489 "icon BLOB," + 490 "uri TEXT," + 491 "displayMode INTEGER," + 492 "appWidgetProvider TEXT," + 493 "modified INTEGER NOT NULL DEFAULT 0," + 494 "restored INTEGER NOT NULL DEFAULT 0," + 495 "profileId INTEGER DEFAULT " + userSerialNumber + "," + 496 "rank INTEGER NOT NULL DEFAULT 0," + 497 "options INTEGER NOT NULL DEFAULT 0" + 498 ");"); 499 addWorkspacesTable(db); 500 501 // Database was just created, so wipe any previous widgets 502 if (mAppWidgetHost != null) { 503 mAppWidgetHost.deleteHost(); 504 505 /** 506 * Send notification that we've deleted the {@link AppWidgetHost}, 507 * probably as part of the initial database creation. The receiver may 508 * want to re-call {@link AppWidgetHost#startListening()} to ensure 509 * callbacks are correctly set. 510 */ 511 LauncherAppState.getLauncherProvider().notifyAppWidgetHostReset(); 512 } 513 514 // Fresh and clean launcher DB. 515 mMaxItemId = initializeMaxItemId(db); 516 setFlagEmptyDbCreated(); 517 518 // When a new DB is created, remove all previously stored managed profile information. 519 ManagedProfileHeuristic.processAllUsers(Collections.<UserHandleCompat>emptyList(), mContext); 520 } 521 522 private void addWorkspacesTable(SQLiteDatabase db) { 523 db.execSQL("CREATE TABLE " + TABLE_WORKSPACE_SCREENS + " (" + 524 LauncherSettings.WorkspaceScreens._ID + " INTEGER PRIMARY KEY," + 525 LauncherSettings.WorkspaceScreens.SCREEN_RANK + " INTEGER," + 526 LauncherSettings.ChangeLogColumns.MODIFIED + " INTEGER NOT NULL DEFAULT 0" + 527 ");"); 528 } 529 530 private void removeOrphanedItems(SQLiteDatabase db) { 531 // Delete items directly on the workspace who's screen id doesn't exist 532 // "DELETE FROM favorites WHERE screen NOT IN (SELECT _id FROM workspaceScreens) 533 // AND container = -100" 534 String removeOrphanedDesktopItems = "DELETE FROM " + TABLE_FAVORITES + 535 " WHERE " + 536 LauncherSettings.Favorites.SCREEN + " NOT IN (SELECT " + 537 LauncherSettings.WorkspaceScreens._ID + " FROM " + TABLE_WORKSPACE_SCREENS + ")" + 538 " AND " + 539 LauncherSettings.Favorites.CONTAINER + " = " + 540 LauncherSettings.Favorites.CONTAINER_DESKTOP; 541 db.execSQL(removeOrphanedDesktopItems); 542 543 // Delete items contained in folders which no longer exist (after above statement) 544 // "DELETE FROM favorites WHERE container <> -100 AND container <> -101 AND container 545 // NOT IN (SELECT _id FROM favorites WHERE itemType = 2)" 546 String removeOrphanedFolderItems = "DELETE FROM " + TABLE_FAVORITES + 547 " WHERE " + 548 LauncherSettings.Favorites.CONTAINER + " <> " + 549 LauncherSettings.Favorites.CONTAINER_DESKTOP + 550 " AND " 551 + LauncherSettings.Favorites.CONTAINER + " <> " + 552 LauncherSettings.Favorites.CONTAINER_HOTSEAT + 553 " AND " 554 + LauncherSettings.Favorites.CONTAINER + " NOT IN (SELECT " + 555 LauncherSettings.Favorites._ID + " FROM " + TABLE_FAVORITES + 556 " WHERE " + LauncherSettings.Favorites.ITEM_TYPE + " = " + 557 LauncherSettings.Favorites.ITEM_TYPE_FOLDER + ")"; 558 db.execSQL(removeOrphanedFolderItems); 559 } 560 561 private void setFlagJustLoadedOldDb() { 562 String spKey = LauncherAppState.getSharedPreferencesKey(); 563 SharedPreferences sp = mContext.getSharedPreferences(spKey, Context.MODE_PRIVATE); 564 sp.edit().putBoolean(EMPTY_DATABASE_CREATED, false).commit(); 565 } 566 567 private void setFlagEmptyDbCreated() { 568 String spKey = LauncherAppState.getSharedPreferencesKey(); 569 SharedPreferences sp = mContext.getSharedPreferences(spKey, Context.MODE_PRIVATE); 570 sp.edit().putBoolean(EMPTY_DATABASE_CREATED, true).commit(); 571 } 572 573 @Override 574 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 575 if (LOGD) Log.d(TAG, "onUpgrade triggered: " + oldVersion); 576 switch (oldVersion) { 577 // The version cannot be lower that 12, as Launcher3 never supported a lower 578 // version of the DB. 579 case 12: { 580 // With the new shrink-wrapped and re-orderable workspaces, it makes sense 581 // to persist workspace screens and their relative order. 582 mMaxScreenId = 0; 583 addWorkspacesTable(db); 584 } 585 case 13: { 586 db.beginTransaction(); 587 try { 588 // Insert new column for holding widget provider name 589 db.execSQL("ALTER TABLE favorites " + 590 "ADD COLUMN appWidgetProvider TEXT;"); 591 db.setTransactionSuccessful(); 592 } catch (SQLException ex) { 593 Log.e(TAG, ex.getMessage(), ex); 594 // Old version remains, which means we wipe old data 595 break; 596 } finally { 597 db.endTransaction(); 598 } 599 } 600 case 14: { 601 db.beginTransaction(); 602 try { 603 // Insert new column for holding update timestamp 604 db.execSQL("ALTER TABLE favorites " + 605 "ADD COLUMN modified INTEGER NOT NULL DEFAULT 0;"); 606 db.execSQL("ALTER TABLE workspaceScreens " + 607 "ADD COLUMN modified INTEGER NOT NULL DEFAULT 0;"); 608 db.setTransactionSuccessful(); 609 } catch (SQLException ex) { 610 Log.e(TAG, ex.getMessage(), ex); 611 // Old version remains, which means we wipe old data 612 break; 613 } finally { 614 db.endTransaction(); 615 } 616 } 617 case 15: { 618 if (!addIntegerColumn(db, Favorites.RESTORED, 0)) { 619 // Old version remains, which means we wipe old data 620 break; 621 } 622 } 623 case 16: { 624 // We use the db version upgrade here to identify users who may not have seen 625 // clings yet (because they weren't available), but for whom the clings are now 626 // available (tablet users). Because one of the possible cling flows (migration) 627 // is very destructive (wipes out workspaces), we want to prevent this from showing 628 // until clear data. We do so by marking that the clings have been shown. 629 LauncherClings.synchonouslyMarkFirstRunClingDismissed(mContext); 630 } 631 case 17: { 632 // No-op 633 } 634 case 18: { 635 // Due to a data loss bug, some users may have items associated with screen ids 636 // which no longer exist. Since this can cause other problems, and since the user 637 // will never see these items anyway, we use database upgrade as an opportunity to 638 // clean things up. 639 removeOrphanedItems(db); 640 } 641 case 19: { 642 // Add userId column 643 if (!addProfileColumn(db)) { 644 // Old version remains, which means we wipe old data 645 break; 646 } 647 } 648 case 20: 649 if (!updateFolderItemsRank(db, true)) { 650 break; 651 } 652 case 21: 653 // Recreate workspace table with screen id a primary key 654 if (!recreateWorkspaceTable(db)) { 655 break; 656 } 657 case 22: { 658 if (!addIntegerColumn(db, Favorites.OPTIONS, 0)) { 659 // Old version remains, which means we wipe old data 660 break; 661 } 662 } 663 case 23: 664 // No-op 665 case 24: 666 ManagedProfileHeuristic.markExistingUsersForNoFolderCreation(mContext); 667 case 25: 668 convertShortcutsToLauncherActivities(db); 669 case 26: { 670 // DB Upgraded successfully 671 return; 672 } 673 } 674 675 // DB was not upgraded 676 Log.w(TAG, "Destroying all old data."); 677 createEmptyDB(db); 678 } 679 680 @Override 681 public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) { 682 // This shouldn't happen -- throw our hands up in the air and start over. 683 Log.w(TAG, "Database version downgrade from: " + oldVersion + " to " + newVersion + 684 ". Wiping databse."); 685 createEmptyDB(db); 686 } 687 688 /** 689 * Clears all the data for a fresh start. 690 */ 691 public void createEmptyDB(SQLiteDatabase db) { 692 db.execSQL("DROP TABLE IF EXISTS " + TABLE_FAVORITES); 693 db.execSQL("DROP TABLE IF EXISTS " + TABLE_WORKSPACE_SCREENS); 694 onCreate(db); 695 } 696 697 /** 698 * Replaces all shortcuts of type {@link Favorites#ITEM_TYPE_SHORTCUT} which have a valid 699 * launcher activity target with {@link Favorites#ITEM_TYPE_APPLICATION}. 700 */ 701 @Thunk void convertShortcutsToLauncherActivities(SQLiteDatabase db) { 702 db.beginTransaction(); 703 Cursor c = null; 704 SQLiteStatement updateStmt = null; 705 706 try { 707 // Only consider the primary user as other users can't have a shortcut. 708 long userSerial = UserManagerCompat.getInstance(mContext) 709 .getSerialNumberForUser(UserHandleCompat.myUserHandle()); 710 c = db.query(TABLE_FAVORITES, new String[] { 711 Favorites._ID, 712 Favorites.INTENT, 713 }, "itemType=" + Favorites.ITEM_TYPE_SHORTCUT + " AND profileId=" + userSerial, 714 null, null, null, null); 715 716 updateStmt = db.compileStatement("UPDATE favorites SET itemType=" 717 + Favorites.ITEM_TYPE_APPLICATION + " WHERE _id=?"); 718 719 final int idIndex = c.getColumnIndexOrThrow(Favorites._ID); 720 final int intentIndex = c.getColumnIndexOrThrow(Favorites.INTENT); 721 722 while (c.moveToNext()) { 723 String intentDescription = c.getString(intentIndex); 724 Intent intent; 725 try { 726 intent = Intent.parseUri(intentDescription, 0); 727 } catch (URISyntaxException e) { 728 Log.e(TAG, "Unable to parse intent", e); 729 continue; 730 } 731 732 if (!InstallShortcutReceiver.isLauncherActivity(intent, mContext)) { 733 continue; 734 } 735 736 long id = c.getLong(idIndex); 737 updateStmt.bindLong(1, id); 738 updateStmt.execute(); 739 } 740 db.setTransactionSuccessful(); 741 } catch (SQLException ex) { 742 Log.w(TAG, "Error deduping shortcuts", ex); 743 } finally { 744 db.endTransaction(); 745 if (c != null) { 746 c.close(); 747 } 748 if (updateStmt != null) { 749 updateStmt.close(); 750 } 751 } 752 } 753 754 /** 755 * Recreates workspace table and migrates data to the new table. 756 */ 757 public boolean recreateWorkspaceTable(SQLiteDatabase db) { 758 db.beginTransaction(); 759 try { 760 Cursor c = db.query(TABLE_WORKSPACE_SCREENS, 761 new String[] {LauncherSettings.WorkspaceScreens._ID}, 762 null, null, null, null, 763 LauncherSettings.WorkspaceScreens.SCREEN_RANK); 764 ArrayList<Long> sortedIDs = new ArrayList<Long>(); 765 long maxId = 0; 766 try { 767 while (c.moveToNext()) { 768 Long id = c.getLong(0); 769 if (!sortedIDs.contains(id)) { 770 sortedIDs.add(id); 771 maxId = Math.max(maxId, id); 772 } 773 } 774 } finally { 775 c.close(); 776 } 777 778 db.execSQL("DROP TABLE IF EXISTS " + TABLE_WORKSPACE_SCREENS); 779 addWorkspacesTable(db); 780 781 // Add all screen ids back 782 int total = sortedIDs.size(); 783 for (int i = 0; i < total; i++) { 784 ContentValues values = new ContentValues(); 785 values.put(LauncherSettings.WorkspaceScreens._ID, sortedIDs.get(i)); 786 values.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, i); 787 addModifiedTime(values); 788 db.insertOrThrow(TABLE_WORKSPACE_SCREENS, null, values); 789 } 790 db.setTransactionSuccessful(); 791 mMaxScreenId = maxId; 792 } catch (SQLException ex) { 793 // Old version remains, which means we wipe old data 794 Log.e(TAG, ex.getMessage(), ex); 795 return false; 796 } finally { 797 db.endTransaction(); 798 } 799 return true; 800 } 801 802 @Thunk boolean updateFolderItemsRank(SQLiteDatabase db, boolean addRankColumn) { 803 db.beginTransaction(); 804 try { 805 if (addRankColumn) { 806 // Insert new column for holding rank 807 db.execSQL("ALTER TABLE favorites ADD COLUMN rank INTEGER NOT NULL DEFAULT 0;"); 808 } 809 810 // Get a map for folder ID to folder width 811 Cursor c = db.rawQuery("SELECT container, MAX(cellX) FROM favorites" 812 + " WHERE container IN (SELECT _id FROM favorites WHERE itemType = ?)" 813 + " GROUP BY container;", 814 new String[] {Integer.toString(LauncherSettings.Favorites.ITEM_TYPE_FOLDER)}); 815 816 while (c.moveToNext()) { 817 db.execSQL("UPDATE favorites SET rank=cellX+(cellY*?) WHERE " 818 + "container=? AND cellX IS NOT NULL AND cellY IS NOT NULL;", 819 new Object[] {c.getLong(1) + 1, c.getLong(0)}); 820 } 821 822 c.close(); 823 db.setTransactionSuccessful(); 824 } catch (SQLException ex) { 825 // Old version remains, which means we wipe old data 826 Log.e(TAG, ex.getMessage(), ex); 827 return false; 828 } finally { 829 db.endTransaction(); 830 } 831 return true; 832 } 833 834 private boolean addProfileColumn(SQLiteDatabase db) { 835 UserManagerCompat userManager = UserManagerCompat.getInstance(mContext); 836 // Default to the serial number of this user, for older 837 // shortcuts. 838 long userSerialNumber = userManager.getSerialNumberForUser( 839 UserHandleCompat.myUserHandle()); 840 return addIntegerColumn(db, Favorites.PROFILE_ID, userSerialNumber); 841 } 842 843 private boolean addIntegerColumn(SQLiteDatabase db, String columnName, long defaultValue) { 844 db.beginTransaction(); 845 try { 846 db.execSQL("ALTER TABLE favorites ADD COLUMN " 847 + columnName + " INTEGER NOT NULL DEFAULT " + defaultValue + ";"); 848 db.setTransactionSuccessful(); 849 } catch (SQLException ex) { 850 Log.e(TAG, ex.getMessage(), ex); 851 return false; 852 } finally { 853 db.endTransaction(); 854 } 855 return true; 856 } 857 858 // Generates a new ID to use for an object in your database. This method should be only 859 // called from the main UI thread. As an exception, we do call it when we call the 860 // constructor from the worker thread; however, this doesn't extend until after the 861 // constructor is called, and we only pass a reference to LauncherProvider to LauncherApp 862 // after that point 863 @Override 864 public long generateNewItemId() { 865 if (mMaxItemId < 0) { 866 throw new RuntimeException("Error: max item id was not initialized"); 867 } 868 mMaxItemId += 1; 869 return mMaxItemId; 870 } 871 872 @Override 873 public long insertAndCheck(SQLiteDatabase db, ContentValues values) { 874 return dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values); 875 } 876 877 public void updateMaxItemId(long id) { 878 mMaxItemId = id + 1; 879 } 880 881 public void checkId(String table, ContentValues values) { 882 long id = values.getAsLong(LauncherSettings.BaseLauncherColumns._ID); 883 if (table == LauncherProvider.TABLE_WORKSPACE_SCREENS) { 884 mMaxScreenId = Math.max(id, mMaxScreenId); 885 } else { 886 mMaxItemId = Math.max(id, mMaxItemId); 887 } 888 } 889 890 private long initializeMaxItemId(SQLiteDatabase db) { 891 return getMaxId(db, TABLE_FAVORITES); 892 } 893 894 // Generates a new ID to use for an workspace screen in your database. This method 895 // should be only called from the main UI thread. As an exception, we do call it when we 896 // call the constructor from the worker thread; however, this doesn't extend until after the 897 // constructor is called, and we only pass a reference to LauncherProvider to LauncherApp 898 // after that point 899 public long generateNewScreenId() { 900 if (mMaxScreenId < 0) { 901 throw new RuntimeException("Error: max screen id was not initialized"); 902 } 903 mMaxScreenId += 1; 904 return mMaxScreenId; 905 } 906 907 private long initializeMaxScreenId(SQLiteDatabase db) { 908 return getMaxId(db, TABLE_WORKSPACE_SCREENS); 909 } 910 911 @Thunk boolean initializeExternalAdd(ContentValues values) { 912 // 1. Ensure that externally added items have a valid item id 913 long id = generateNewItemId(); 914 values.put(LauncherSettings.Favorites._ID, id); 915 916 // 2. In the case of an app widget, and if no app widget id is specified, we 917 // attempt allocate and bind the widget. 918 Integer itemType = values.getAsInteger(LauncherSettings.Favorites.ITEM_TYPE); 919 if (itemType != null && 920 itemType.intValue() == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET && 921 !values.containsKey(LauncherSettings.Favorites.APPWIDGET_ID)) { 922 923 final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext); 924 ComponentName cn = ComponentName.unflattenFromString( 925 values.getAsString(Favorites.APPWIDGET_PROVIDER)); 926 927 if (cn != null) { 928 try { 929 int appWidgetId = mAppWidgetHost.allocateAppWidgetId(); 930 values.put(LauncherSettings.Favorites.APPWIDGET_ID, appWidgetId); 931 if (!appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId,cn)) { 932 return false; 933 } 934 } catch (RuntimeException e) { 935 Log.e(TAG, "Failed to initialize external widget", e); 936 return false; 937 } 938 } else { 939 return false; 940 } 941 } 942 943 // Add screen id if not present 944 long screenId = values.getAsLong(LauncherSettings.Favorites.SCREEN); 945 if (!addScreenIdIfNecessary(screenId)) { 946 return false; 947 } 948 return true; 949 } 950 951 // Returns true of screen id exists, or if successfully added 952 private boolean addScreenIdIfNecessary(long screenId) { 953 if (!hasScreenId(screenId)) { 954 int rank = getMaxScreenRank() + 1; 955 956 ContentValues v = new ContentValues(); 957 v.put(LauncherSettings.WorkspaceScreens._ID, screenId); 958 v.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, rank); 959 if (dbInsertAndCheck(this, getWritableDatabase(), 960 TABLE_WORKSPACE_SCREENS, null, v) < 0) { 961 return false; 962 } 963 } 964 return true; 965 } 966 967 private boolean hasScreenId(long screenId) { 968 SQLiteDatabase db = getWritableDatabase(); 969 Cursor c = db.rawQuery("SELECT * FROM " + TABLE_WORKSPACE_SCREENS + " WHERE " 970 + LauncherSettings.WorkspaceScreens._ID + " = " + screenId, null); 971 if (c != null) { 972 int count = c.getCount(); 973 c.close(); 974 return count > 0; 975 } else { 976 return false; 977 } 978 } 979 980 private int getMaxScreenRank() { 981 SQLiteDatabase db = getWritableDatabase(); 982 Cursor c = db.rawQuery("SELECT MAX(" + LauncherSettings.WorkspaceScreens.SCREEN_RANK 983 + ") FROM " + TABLE_WORKSPACE_SCREENS, null); 984 985 // get the result 986 final int maxRankIndex = 0; 987 int rank = -1; 988 if (c != null && c.moveToNext()) { 989 rank = c.getInt(maxRankIndex); 990 } 991 if (c != null) { 992 c.close(); 993 } 994 995 return rank; 996 } 997 998 @Thunk int loadFavorites(SQLiteDatabase db, AutoInstallsLayout loader) { 999 ArrayList<Long> screenIds = new ArrayList<Long>(); 1000 // TODO: Use multiple loaders with fall-back and transaction. 1001 int count = loader.loadLayout(db, screenIds); 1002 1003 // Add the screens specified by the items above 1004 Collections.sort(screenIds); 1005 int rank = 0; 1006 ContentValues values = new ContentValues(); 1007 for (Long id : screenIds) { 1008 values.clear(); 1009 values.put(LauncherSettings.WorkspaceScreens._ID, id); 1010 values.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, rank); 1011 if (dbInsertAndCheck(this, db, TABLE_WORKSPACE_SCREENS, null, values) < 0) { 1012 throw new RuntimeException("Failed initialize screen table" 1013 + "from default layout"); 1014 } 1015 rank++; 1016 } 1017 1018 // Ensure that the max ids are initialized 1019 mMaxItemId = initializeMaxItemId(db); 1020 mMaxScreenId = initializeMaxScreenId(db); 1021 1022 return count; 1023 } 1024 1025 @Thunk void migrateLauncher2Shortcuts(SQLiteDatabase db, Uri uri) { 1026 final ContentResolver resolver = mContext.getContentResolver(); 1027 Cursor c = null; 1028 int count = 0; 1029 int curScreen = 0; 1030 1031 try { 1032 c = resolver.query(uri, null, null, null, "title ASC"); 1033 } catch (Exception e) { 1034 // Ignore 1035 } 1036 1037 // We already have a favorites database in the old provider 1038 if (c != null) { 1039 try { 1040 if (c.getCount() > 0) { 1041 final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID); 1042 final int intentIndex 1043 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.INTENT); 1044 final int titleIndex 1045 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE); 1046 final int iconTypeIndex 1047 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_TYPE); 1048 final int iconIndex 1049 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON); 1050 final int iconPackageIndex 1051 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_PACKAGE); 1052 final int iconResourceIndex 1053 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_RESOURCE); 1054 final int containerIndex 1055 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER); 1056 final int itemTypeIndex 1057 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE); 1058 final int screenIndex 1059 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN); 1060 final int cellXIndex 1061 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX); 1062 final int cellYIndex 1063 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY); 1064 final int uriIndex 1065 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.URI); 1066 final int displayModeIndex 1067 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.DISPLAY_MODE); 1068 final int profileIndex 1069 = c.getColumnIndex(LauncherSettings.Favorites.PROFILE_ID); 1070 1071 int i = 0; 1072 int curX = 0; 1073 int curY = 0; 1074 1075 final LauncherAppState app = LauncherAppState.getInstance(); 1076 final InvariantDeviceProfile profile = app.getInvariantDeviceProfile(); 1077 final int width = (int) profile.numColumns; 1078 final int height = (int) profile.numRows; 1079 final int hotseatWidth = (int) profile.numHotseatIcons; 1080 1081 final HashSet<String> seenIntents = new HashSet<String>(c.getCount()); 1082 1083 final ArrayList<ContentValues> shortcuts = new ArrayList<ContentValues>(); 1084 final ArrayList<ContentValues> folders = new ArrayList<ContentValues>(); 1085 final SparseArray<ContentValues> hotseat = new SparseArray<ContentValues>(); 1086 1087 while (c.moveToNext()) { 1088 final int itemType = c.getInt(itemTypeIndex); 1089 if (itemType != Favorites.ITEM_TYPE_APPLICATION 1090 && itemType != Favorites.ITEM_TYPE_SHORTCUT 1091 && itemType != Favorites.ITEM_TYPE_FOLDER) { 1092 continue; 1093 } 1094 1095 final int cellX = c.getInt(cellXIndex); 1096 final int cellY = c.getInt(cellYIndex); 1097 final int screen = c.getInt(screenIndex); 1098 int container = c.getInt(containerIndex); 1099 final String intentStr = c.getString(intentIndex); 1100 1101 UserManagerCompat userManager = UserManagerCompat.getInstance(mContext); 1102 UserHandleCompat userHandle; 1103 final long userSerialNumber; 1104 if (profileIndex != -1 && !c.isNull(profileIndex)) { 1105 userSerialNumber = c.getInt(profileIndex); 1106 userHandle = userManager.getUserForSerialNumber(userSerialNumber); 1107 } else { 1108 // Default to the serial number of this user, for older 1109 // shortcuts. 1110 userHandle = UserHandleCompat.myUserHandle(); 1111 userSerialNumber = userManager.getSerialNumberForUser(userHandle); 1112 } 1113 1114 if (userHandle == null) { 1115 Launcher.addDumpLog(TAG, "skipping deleted user", true); 1116 continue; 1117 } 1118 1119 Launcher.addDumpLog(TAG, "migrating \"" 1120 + c.getString(titleIndex) + "\" (" 1121 + cellX + "," + cellY + "@" 1122 + LauncherSettings.Favorites.containerToString(container) 1123 + "/" + screen 1124 + "): " + intentStr, true); 1125 1126 if (itemType != Favorites.ITEM_TYPE_FOLDER) { 1127 1128 final Intent intent; 1129 final ComponentName cn; 1130 try { 1131 intent = Intent.parseUri(intentStr, 0); 1132 } catch (URISyntaxException e) { 1133 // bogus intent? 1134 Launcher.addDumpLog(TAG, 1135 "skipping invalid intent uri", true); 1136 continue; 1137 } 1138 1139 cn = intent.getComponent(); 1140 if (TextUtils.isEmpty(intentStr)) { 1141 // no intent? no icon 1142 Launcher.addDumpLog(TAG, "skipping empty intent", true); 1143 continue; 1144 } else if (cn != null && 1145 !LauncherModel.isValidPackageActivity(mContext, cn, 1146 userHandle)) { 1147 // component no longer exists. 1148 Launcher.addDumpLog(TAG, "skipping item whose component " + 1149 "no longer exists.", true); 1150 continue; 1151 } else if (container == 1152 LauncherSettings.Favorites.CONTAINER_DESKTOP) { 1153 // Dedupe icons directly on the workspace 1154 1155 // Canonicalize 1156 // the Play Store sets the package parameter, but Launcher 1157 // does not, so we clear that out to keep them the same. 1158 // Also ignore intent flags for the purposes of deduping. 1159 intent.setPackage(null); 1160 int flags = intent.getFlags(); 1161 intent.setFlags(0); 1162 final String key = intent.toUri(0); 1163 intent.setFlags(flags); 1164 if (seenIntents.contains(key)) { 1165 Launcher.addDumpLog(TAG, "skipping duplicate", true); 1166 continue; 1167 } else { 1168 seenIntents.add(key); 1169 } 1170 } 1171 } 1172 1173 ContentValues values = new ContentValues(c.getColumnCount()); 1174 values.put(LauncherSettings.Favorites._ID, c.getInt(idIndex)); 1175 values.put(LauncherSettings.Favorites.INTENT, intentStr); 1176 values.put(LauncherSettings.Favorites.TITLE, c.getString(titleIndex)); 1177 values.put(LauncherSettings.Favorites.ICON_TYPE, 1178 c.getInt(iconTypeIndex)); 1179 values.put(LauncherSettings.Favorites.ICON, c.getBlob(iconIndex)); 1180 values.put(LauncherSettings.Favorites.ICON_PACKAGE, 1181 c.getString(iconPackageIndex)); 1182 values.put(LauncherSettings.Favorites.ICON_RESOURCE, 1183 c.getString(iconResourceIndex)); 1184 values.put(LauncherSettings.Favorites.ITEM_TYPE, itemType); 1185 values.put(LauncherSettings.Favorites.APPWIDGET_ID, -1); 1186 values.put(LauncherSettings.Favorites.URI, c.getString(uriIndex)); 1187 values.put(LauncherSettings.Favorites.DISPLAY_MODE, 1188 c.getInt(displayModeIndex)); 1189 values.put(LauncherSettings.Favorites.PROFILE_ID, userSerialNumber); 1190 1191 if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { 1192 hotseat.put(screen, values); 1193 } 1194 1195 if (container != LauncherSettings.Favorites.CONTAINER_DESKTOP) { 1196 // In a folder or in the hotseat, preserve position 1197 values.put(LauncherSettings.Favorites.SCREEN, screen); 1198 values.put(LauncherSettings.Favorites.CELLX, cellX); 1199 values.put(LauncherSettings.Favorites.CELLY, cellY); 1200 } else { 1201 // For items contained directly on one of the workspace screen, 1202 // we'll determine their location (screen, x, y) in a second pass. 1203 } 1204 1205 values.put(LauncherSettings.Favorites.CONTAINER, container); 1206 1207 if (itemType != Favorites.ITEM_TYPE_FOLDER) { 1208 shortcuts.add(values); 1209 } else { 1210 folders.add(values); 1211 } 1212 } 1213 1214 // Now that we have all the hotseat icons, let's go through them left-right 1215 // and assign valid locations for them in the new hotseat 1216 final int N = hotseat.size(); 1217 for (int idx=0; idx<N; idx++) { 1218 int hotseatX = hotseat.keyAt(idx); 1219 ContentValues values = hotseat.valueAt(idx); 1220 1221 if (hotseatX == profile.hotseatAllAppsRank) { 1222 // let's drop this in the next available hole in the hotseat 1223 while (++hotseatX < hotseatWidth) { 1224 if (hotseat.get(hotseatX) == null) { 1225 // found a spot! move it here 1226 values.put(LauncherSettings.Favorites.SCREEN, 1227 hotseatX); 1228 break; 1229 } 1230 } 1231 } 1232 if (hotseatX >= hotseatWidth) { 1233 // no room for you in the hotseat? it's off to the desktop with you 1234 values.put(LauncherSettings.Favorites.CONTAINER, 1235 Favorites.CONTAINER_DESKTOP); 1236 } 1237 } 1238 1239 final ArrayList<ContentValues> allItems = new ArrayList<ContentValues>(); 1240 // Folders first 1241 allItems.addAll(folders); 1242 // Then shortcuts 1243 allItems.addAll(shortcuts); 1244 1245 // Layout all the folders 1246 for (ContentValues values: allItems) { 1247 if (values.getAsInteger(LauncherSettings.Favorites.CONTAINER) != 1248 LauncherSettings.Favorites.CONTAINER_DESKTOP) { 1249 // Hotseat items and folder items have already had their 1250 // location information set. Nothing to be done here. 1251 continue; 1252 } 1253 values.put(LauncherSettings.Favorites.SCREEN, curScreen); 1254 values.put(LauncherSettings.Favorites.CELLX, curX); 1255 values.put(LauncherSettings.Favorites.CELLY, curY); 1256 curX = (curX + 1) % width; 1257 if (curX == 0) { 1258 curY = (curY + 1); 1259 } 1260 // Leave the last row of icons blank on every screen 1261 if (curY == height - 1) { 1262 curScreen = (int) generateNewScreenId(); 1263 curY = 0; 1264 } 1265 } 1266 1267 if (allItems.size() > 0) { 1268 db.beginTransaction(); 1269 try { 1270 for (ContentValues row: allItems) { 1271 if (row == null) continue; 1272 if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, row) 1273 < 0) { 1274 return; 1275 } else { 1276 count++; 1277 } 1278 } 1279 db.setTransactionSuccessful(); 1280 } finally { 1281 db.endTransaction(); 1282 } 1283 } 1284 1285 db.beginTransaction(); 1286 try { 1287 for (i=0; i<=curScreen; i++) { 1288 final ContentValues values = new ContentValues(); 1289 values.put(LauncherSettings.WorkspaceScreens._ID, i); 1290 values.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, i); 1291 if (dbInsertAndCheck(this, db, TABLE_WORKSPACE_SCREENS, null, values) 1292 < 0) { 1293 return; 1294 } 1295 } 1296 db.setTransactionSuccessful(); 1297 } finally { 1298 db.endTransaction(); 1299 } 1300 1301 updateFolderItemsRank(db, false); 1302 } 1303 } finally { 1304 c.close(); 1305 } 1306 } 1307 1308 Launcher.addDumpLog(TAG, "migrated " + count + " icons from Launcher2 into " 1309 + (curScreen+1) + " screens", true); 1310 1311 // ensure that new screens are created to hold these icons 1312 setFlagJustLoadedOldDb(); 1313 1314 // Update max IDs; very important since we just grabbed IDs from another database 1315 mMaxItemId = initializeMaxItemId(db); 1316 mMaxScreenId = initializeMaxScreenId(db); 1317 if (LOGD) Log.d(TAG, "mMaxItemId: " + mMaxItemId + " mMaxScreenId: " + mMaxScreenId); 1318 } 1319 } 1320 1321 /** 1322 * @return the max _id in the provided table. 1323 */ 1324 @Thunk static long getMaxId(SQLiteDatabase db, String table) { 1325 Cursor c = db.rawQuery("SELECT MAX(_id) FROM " + table, null); 1326 // get the result 1327 long id = -1; 1328 if (c != null && c.moveToNext()) { 1329 id = c.getLong(0); 1330 } 1331 if (c != null) { 1332 c.close(); 1333 } 1334 1335 if (id == -1) { 1336 throw new RuntimeException("Error: could not query max id in " + table); 1337 } 1338 1339 return id; 1340 } 1341 1342 static class SqlArguments { 1343 public final String table; 1344 public final String where; 1345 public final String[] args; 1346 1347 SqlArguments(Uri url, String where, String[] args) { 1348 if (url.getPathSegments().size() == 1) { 1349 this.table = url.getPathSegments().get(0); 1350 this.where = where; 1351 this.args = args; 1352 } else if (url.getPathSegments().size() != 2) { 1353 throw new IllegalArgumentException("Invalid URI: " + url); 1354 } else if (!TextUtils.isEmpty(where)) { 1355 throw new UnsupportedOperationException("WHERE clause not supported: " + url); 1356 } else { 1357 this.table = url.getPathSegments().get(0); 1358 this.where = "_id=" + ContentUris.parseId(url); 1359 this.args = null; 1360 } 1361 } 1362 1363 SqlArguments(Uri url) { 1364 if (url.getPathSegments().size() == 1) { 1365 table = url.getPathSegments().get(0); 1366 where = null; 1367 args = null; 1368 } else { 1369 throw new IllegalArgumentException("Invalid URI: " + url); 1370 } 1371 } 1372 } 1373} 1374