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