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.appwidget.AppWidgetHost; 20 import android.appwidget.AppWidgetManager; 21 import android.appwidget.AppWidgetProviderInfo; 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.res.Resources; 34 import android.database.Cursor; 35 import android.database.SQLException; 36 import android.database.sqlite.SQLiteDatabase; 37 import android.database.sqlite.SQLiteOpenHelper; 38 import android.database.sqlite.SQLiteQueryBuilder; 39 import android.database.sqlite.SQLiteStatement; 40 import android.graphics.Bitmap; 41 import android.graphics.BitmapFactory; 42 import android.net.Uri; 43 import android.provider.Settings; 44 import android.text.TextUtils; 45 import android.util.Log; 46 import android.util.SparseArray; 47 48 import com.android.launcher3.AutoInstallsLayout.LayoutParserCallback; 49 import com.android.launcher3.LauncherSettings.Favorites; 50 import com.android.launcher3.compat.UserHandleCompat; 51 import com.android.launcher3.compat.UserManagerCompat; 52 import com.android.launcher3.config.ProviderConfig; 53 54 import java.io.File; 55 import java.net.URISyntaxException; 56 import java.util.ArrayList; 57 import java.util.Collections; 58 import java.util.HashSet; 59 60 public class LauncherProvider extends ContentProvider { 61 private static final String TAG = "Launcher.LauncherProvider"; 62 private static final boolean LOGD = false; 63 64 private static final int DATABASE_VERSION = 20; 65 66 static final String OLD_AUTHORITY = "com.android.launcher2.settings"; 67 static final String AUTHORITY = ProviderConfig.AUTHORITY; 68 69 // Should we attempt to load anything from the com.android.launcher2 provider? 70 static final boolean IMPORT_LAUNCHER2_DATABASE = false; 71 72 static final String TABLE_FAVORITES = "favorites"; 73 static final String TABLE_WORKSPACE_SCREENS = "workspaceScreens"; 74 static final String PARAMETER_NOTIFY = "notify"; 75 static final String UPGRADED_FROM_OLD_DATABASE = 76 "UPGRADED_FROM_OLD_DATABASE"; 77 static final String EMPTY_DATABASE_CREATED = 78 "EMPTY_DATABASE_CREATED"; 79 80 private static final String URI_PARAM_IS_EXTERNAL_ADD = "isExternalAdd"; 81 82 private LauncherProviderChangeListener mListener; 83 84 /** 85 * {@link Uri} triggered at any registered {@link android.database.ContentObserver} when 86 * {@link AppWidgetHost#deleteHost()} is called during database creation. 87 * Use this to recall {@link AppWidgetHost#startListening()} if needed. 88 */ 89 static final Uri CONTENT_APPWIDGET_RESET_URI = 90 Uri.parse("content://" + AUTHORITY + "/appWidgetReset"); 91 92 private DatabaseHelper mOpenHelper; 93 private static boolean sJustLoadedFromOldDb; 94 95 @Override 96 public boolean onCreate() { 97 final Context context = getContext(); 98 mOpenHelper = new DatabaseHelper(context); 99 LauncherAppState.setLauncherProvider(this); 100 return true; 101 } 102 103 public boolean wasNewDbCreated() { 104 return mOpenHelper.wasNewDbCreated(); 105 } 106 107 public void setLauncherProviderChangeListener(LauncherProviderChangeListener listener) { 108 mListener = listener; 109 } 110 111 @Override 112 public String getType(Uri uri) { 113 SqlArguments args = new SqlArguments(uri, null, null); 114 if (TextUtils.isEmpty(args.where)) { 115 return "vnd.android.cursor.dir/" + args.table; 116 } else { 117 return "vnd.android.cursor.item/" + args.table; 118 } 119 } 120 121 @Override 122 public Cursor query(Uri uri, String[] projection, String selection, 123 String[] selectionArgs, String sortOrder) { 124 125 SqlArguments args = new SqlArguments(uri, selection, selectionArgs); 126 SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); 127 qb.setTables(args.table); 128 129 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 130 Cursor result = qb.query(db, projection, args.where, args.args, null, null, sortOrder); 131 result.setNotificationUri(getContext().getContentResolver(), uri); 132 133 return result; 134 } 135 136 private static long dbInsertAndCheck(DatabaseHelper helper, 137 SQLiteDatabase db, String table, String nullColumnHack, ContentValues values) { 138 if (values == null) { 139 throw new RuntimeException("Error: attempting to insert null values"); 140 } 141 if (!values.containsKey(LauncherSettings.ChangeLogColumns._ID)) { 142 throw new RuntimeException("Error: attempting to add item without specifying an id"); 143 } 144 helper.checkId(table, values); 145 return db.insert(table, nullColumnHack, values); 146 } 147 148 @Override 149 public Uri insert(Uri uri, ContentValues initialValues) { 150 SqlArguments args = new SqlArguments(uri); 151 152 // In very limited cases, we support system|signature permission apps to add to the db 153 String externalAdd = uri.getQueryParameter(URI_PARAM_IS_EXTERNAL_ADD); 154 if (externalAdd != null && "true".equals(externalAdd)) { 155 if (!mOpenHelper.initializeExternalAdd(initialValues)) { 156 return null; 157 } 158 } 159 160 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 161 addModifiedTime(initialValues); 162 final long rowId = dbInsertAndCheck(mOpenHelper, db, args.table, null, initialValues); 163 if (rowId <= 0) return null; 164 165 uri = ContentUris.withAppendedId(uri, rowId); 166 sendNotify(uri); 167 168 return uri; 169 } 170 171 172 @Override 173 public int bulkInsert(Uri uri, ContentValues[] values) { 174 SqlArguments args = new SqlArguments(uri); 175 176 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 177 db.beginTransaction(); 178 try { 179 int numValues = values.length; 180 for (int i = 0; i < numValues; i++) { 181 addModifiedTime(values[i]); 182 if (dbInsertAndCheck(mOpenHelper, db, args.table, null, values[i]) < 0) { 183 return 0; 184 } 185 } 186 db.setTransactionSuccessful(); 187 } finally { 188 db.endTransaction(); 189 } 190 191 sendNotify(uri); 192 return values.length; 193 } 194 195 @Override 196 public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations) 197 throws OperationApplicationException { 198 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 199 db.beginTransaction(); 200 try { 201 ContentProviderResult[] result = super.applyBatch(operations); 202 db.setTransactionSuccessful(); 203 return result; 204 } finally { 205 db.endTransaction(); 206 } 207 } 208 209 @Override 210 public int delete(Uri uri, String selection, String[] selectionArgs) { 211 SqlArguments args = new SqlArguments(uri, selection, selectionArgs); 212 213 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 214 int count = db.delete(args.table, args.where, args.args); 215 if (count > 0) sendNotify(uri); 216 217 return count; 218 } 219 220 @Override 221 public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { 222 SqlArguments args = new SqlArguments(uri, selection, selectionArgs); 223 224 addModifiedTime(values); 225 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 226 int count = db.update(args.table, values, args.where, args.args); 227 if (count > 0) sendNotify(uri); 228 229 return count; 230 } 231 232 private void sendNotify(Uri uri) { 233 String notify = uri.getQueryParameter(PARAMETER_NOTIFY); 234 if (notify == null || "true".equals(notify)) { 235 getContext().getContentResolver().notifyChange(uri, null); 236 } 237 238 // always notify the backup agent 239 LauncherBackupAgentHelper.dataChanged(getContext()); 240 if (mListener != null) { 241 mListener.onLauncherProviderChange(); 242 } 243 } 244 245 private void addModifiedTime(ContentValues values) { 246 values.put(LauncherSettings.ChangeLogColumns.MODIFIED, System.currentTimeMillis()); 247 } 248 249 public long generateNewItemId() { 250 return mOpenHelper.generateNewItemId(); 251 } 252 253 public void updateMaxItemId(long id) { 254 mOpenHelper.updateMaxItemId(id); 255 } 256 257 public long generateNewScreenId() { 258 return mOpenHelper.generateNewScreenId(); 259 } 260 261 // This is only required one time while loading the workspace during the 262 // upgrade path, and should never be called from anywhere else. 263 public void updateMaxScreenId(long maxScreenId) { 264 mOpenHelper.updateMaxScreenId(maxScreenId); 265 } 266 267 /** 268 * @param Should we load the old db for upgrade? first run only. 269 */ 270 synchronized public boolean justLoadedOldDb() { 271 String spKey = LauncherAppState.getSharedPreferencesKey(); 272 SharedPreferences sp = getContext().getSharedPreferences(spKey, Context.MODE_PRIVATE); 273 274 boolean loadedOldDb = false || sJustLoadedFromOldDb; 275 276 sJustLoadedFromOldDb = false; 277 if (sp.getBoolean(UPGRADED_FROM_OLD_DATABASE, false)) { 278 279 SharedPreferences.Editor editor = sp.edit(); 280 editor.remove(UPGRADED_FROM_OLD_DATABASE); 281 editor.commit(); 282 loadedOldDb = true; 283 } 284 return loadedOldDb; 285 } 286 287 /** 288 * Clears all the data for a fresh start. 289 */ 290 synchronized public void createEmptyDB() { 291 mOpenHelper.createEmptyDB(mOpenHelper.getWritableDatabase()); 292 } 293 294 public void clearFlagEmptyDbCreated() { 295 String spKey = LauncherAppState.getSharedPreferencesKey(); 296 getContext().getSharedPreferences(spKey, Context.MODE_PRIVATE) 297 .edit() 298 .remove(EMPTY_DATABASE_CREATED) 299 .commit(); 300 } 301 302 /** 303 * Loads the default workspace based on the following priority scheme: 304 * 1) From a package provided by play store 305 * 2) From a partner configuration APK, already in the system image 306 * 3) The default configuration for the particular device 307 */ 308 synchronized public void loadDefaultFavoritesIfNecessary() { 309 String spKey = LauncherAppState.getSharedPreferencesKey(); 310 SharedPreferences sp = getContext().getSharedPreferences(spKey, Context.MODE_PRIVATE); 311 312 if (sp.getBoolean(EMPTY_DATABASE_CREATED, false)) { 313 Log.d(TAG, "loading default workspace"); 314 315 AutoInstallsLayout loader = AutoInstallsLayout.get(getContext(), 316 mOpenHelper.mAppWidgetHost, mOpenHelper); 317 318 if (loader == null) { 319 final Partner partner = Partner.get(getContext().getPackageManager()); 320 if (partner != null && partner.hasDefaultLayout()) { 321 final Resources partnerRes = partner.getResources(); 322 int workspaceResId = partnerRes.getIdentifier(Partner.RES_DEFAULT_LAYOUT, 323 "xml", partner.getPackageName()); 324 if (workspaceResId != 0) { 325 loader = new DefaultLayoutParser(getContext(), mOpenHelper.mAppWidgetHost, 326 mOpenHelper, partnerRes, workspaceResId); 327 } 328 } 329 } 330 331 final boolean usingExternallyProvidedLayout = loader != null; 332 if (loader == null) { 333 loader = getDefaultLayoutParser(); 334 } 335 // Populate favorites table with initial favorites 336 if ((mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(), loader) <= 0) 337 && usingExternallyProvidedLayout) { 338 // Unable to load external layout. Cleanup and load the internal layout. 339 createEmptyDB(); 340 mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(), 341 getDefaultLayoutParser()); 342 } 343 clearFlagEmptyDbCreated(); 344 } 345 } 346 347 private DefaultLayoutParser getDefaultLayoutParser() { 348 int defaultLayout = LauncherAppState.getInstance() 349 .getDynamicGrid().getDeviceProfile().defaultLayoutId; 350 return new DefaultLayoutParser(getContext(), mOpenHelper.mAppWidgetHost, 351 mOpenHelper, getContext().getResources(), defaultLayout); 352 } 353 354 public void migrateLauncher2Shortcuts() { 355 mOpenHelper.migrateLauncher2Shortcuts(mOpenHelper.getWritableDatabase(), 356 Uri.parse(getContext().getString(R.string.old_launcher_provider_uri))); 357 } 358 359 private static interface ContentValuesCallback { 360 public void onRow(ContentValues values); 361 } 362 363 private static boolean shouldImportLauncher2Database(Context context) { 364 boolean isTablet = context.getResources().getBoolean(R.bool.is_tablet); 365 366 // We don't import the old databse for tablets, as the grid size has changed. 367 return !isTablet && IMPORT_LAUNCHER2_DATABASE; 368 } 369 370 public void deleteDatabase() { 371 // Are you sure? (y/n) 372 final SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 373 final File dbFile = new File(db.getPath()); 374 mOpenHelper.close(); 375 if (dbFile.exists()) { 376 SQLiteDatabase.deleteDatabase(dbFile); 377 } 378 mOpenHelper = new DatabaseHelper(getContext()); 379 } 380 381 private static class DatabaseHelper extends SQLiteOpenHelper implements LayoutParserCallback { 382 private final Context mContext; 383 private final AppWidgetHost mAppWidgetHost; 384 private long mMaxItemId = -1; 385 private long mMaxScreenId = -1; 386 387 private boolean mNewDbCreated = false; 388 389 DatabaseHelper(Context context) { 390 super(context, LauncherFiles.LAUNCHER_DB, null, DATABASE_VERSION); 391 mContext = context; 392 mAppWidgetHost = new AppWidgetHost(context, Launcher.APPWIDGET_HOST_ID); 393 394 // In the case where neither onCreate nor onUpgrade gets called, we read the maxId from 395 // the DB here 396 if (mMaxItemId == -1) { 397 mMaxItemId = initializeMaxItemId(getWritableDatabase()); 398 } 399 if (mMaxScreenId == -1) { 400 mMaxScreenId = initializeMaxScreenId(getWritableDatabase()); 401 } 402 } 403 404 public boolean wasNewDbCreated() { 405 return mNewDbCreated; 406 } 407 408 /** 409 * Send notification that we've deleted the {@link AppWidgetHost}, 410 * probably as part of the initial database creation. The receiver may 411 * want to re-call {@link AppWidgetHost#startListening()} to ensure 412 * callbacks are correctly set. 413 */ 414 private void sendAppWidgetResetNotify() { 415 final ContentResolver resolver = mContext.getContentResolver(); 416 resolver.notifyChange(CONTENT_APPWIDGET_RESET_URI, null); 417 } 418 419 @Override 420 public void onCreate(SQLiteDatabase db) { 421 if (LOGD) Log.d(TAG, "creating new launcher database"); 422 423 mMaxItemId = 1; 424 mMaxScreenId = 0; 425 mNewDbCreated = true; 426 427 UserManagerCompat userManager = UserManagerCompat.getInstance(mContext); 428 long userSerialNumber = userManager.getSerialNumberForUser( 429 UserHandleCompat.myUserHandle()); 430 431 db.execSQL("CREATE TABLE favorites (" + 432 "_id INTEGER PRIMARY KEY," + 433 "title TEXT," + 434 "intent TEXT," + 435 "container INTEGER," + 436 "screen INTEGER," + 437 "cellX INTEGER," + 438 "cellY INTEGER," + 439 "spanX INTEGER," + 440 "spanY INTEGER," + 441 "itemType INTEGER," + 442 "appWidgetId INTEGER NOT NULL DEFAULT -1," + 443 "isShortcut INTEGER," + 444 "iconType INTEGER," + 445 "iconPackage TEXT," + 446 "iconResource TEXT," + 447 "icon BLOB," + 448 "uri TEXT," + 449 "displayMode INTEGER," + 450 "appWidgetProvider TEXT," + 451 "modified INTEGER NOT NULL DEFAULT 0," + 452 "restored INTEGER NOT NULL DEFAULT 0," + 453 "profileId INTEGER DEFAULT " + userSerialNumber + 454 ");"); 455 addWorkspacesTable(db); 456 457 // Database was just created, so wipe any previous widgets 458 if (mAppWidgetHost != null) { 459 mAppWidgetHost.deleteHost(); 460 sendAppWidgetResetNotify(); 461 } 462 463 if (shouldImportLauncher2Database(mContext)) { 464 // Try converting the old database 465 ContentValuesCallback permuteScreensCb = new ContentValuesCallback() { 466 public void onRow(ContentValues values) { 467 int container = values.getAsInteger(LauncherSettings.Favorites.CONTAINER); 468 if (container == Favorites.CONTAINER_DESKTOP) { 469 int screen = values.getAsInteger(LauncherSettings.Favorites.SCREEN); 470 screen = (int) upgradeLauncherDb_permuteScreens(screen); 471 values.put(LauncherSettings.Favorites.SCREEN, screen); 472 } 473 } 474 }; 475 Uri uri = Uri.parse("content://" + Settings.AUTHORITY + 476 "/old_favorites?notify=true"); 477 if (!convertDatabase(db, uri, permuteScreensCb, true)) { 478 // Try and upgrade from the Launcher2 db 479 uri = Uri.parse(mContext.getString(R.string.old_launcher_provider_uri)); 480 if (!convertDatabase(db, uri, permuteScreensCb, false)) { 481 // If we fail, then set a flag to load the default workspace 482 setFlagEmptyDbCreated(); 483 return; 484 } 485 } 486 // Right now, in non-default workspace cases, we want to run the final 487 // upgrade code (ie. to fix workspace screen indices -> ids, etc.), so 488 // set that flag too. 489 setFlagJustLoadedOldDb(); 490 } else { 491 // Fresh and clean launcher DB. 492 mMaxItemId = initializeMaxItemId(db); 493 setFlagEmptyDbCreated(); 494 } 495 } 496 497 private void addWorkspacesTable(SQLiteDatabase db) { 498 db.execSQL("CREATE TABLE " + TABLE_WORKSPACE_SCREENS + " (" + 499 LauncherSettings.WorkspaceScreens._ID + " INTEGER," + 500 LauncherSettings.WorkspaceScreens.SCREEN_RANK + " INTEGER," + 501 LauncherSettings.ChangeLogColumns.MODIFIED + " INTEGER NOT NULL DEFAULT 0" + 502 ");"); 503 } 504 505 private void removeOrphanedItems(SQLiteDatabase db) { 506 // Delete items directly on the workspace who's screen id doesn't exist 507 // "DELETE FROM favorites WHERE screen NOT IN (SELECT _id FROM workspaceScreens) 508 // AND container = -100" 509 String removeOrphanedDesktopItems = "DELETE FROM " + TABLE_FAVORITES + 510 " WHERE " + 511 LauncherSettings.Favorites.SCREEN + " NOT IN (SELECT " + 512 LauncherSettings.WorkspaceScreens._ID + " FROM " + TABLE_WORKSPACE_SCREENS + ")" + 513 " AND " + 514 LauncherSettings.Favorites.CONTAINER + " = " + 515 LauncherSettings.Favorites.CONTAINER_DESKTOP; 516 db.execSQL(removeOrphanedDesktopItems); 517 518 // Delete items contained in folders which no longer exist (after above statement) 519 // "DELETE FROM favorites WHERE container <> -100 AND container <> -101 AND container 520 // NOT IN (SELECT _id FROM favorites WHERE itemType = 2)" 521 String removeOrphanedFolderItems = "DELETE FROM " + TABLE_FAVORITES + 522 " WHERE " + 523 LauncherSettings.Favorites.CONTAINER + " <> " + 524 LauncherSettings.Favorites.CONTAINER_DESKTOP + 525 " AND " 526 + LauncherSettings.Favorites.CONTAINER + " <> " + 527 LauncherSettings.Favorites.CONTAINER_HOTSEAT + 528 " AND " 529 + LauncherSettings.Favorites.CONTAINER + " NOT IN (SELECT " + 530 LauncherSettings.Favorites._ID + " FROM " + TABLE_FAVORITES + 531 " WHERE " + LauncherSettings.Favorites.ITEM_TYPE + " = " + 532 LauncherSettings.Favorites.ITEM_TYPE_FOLDER + ")"; 533 db.execSQL(removeOrphanedFolderItems); 534 } 535 536 private void setFlagJustLoadedOldDb() { 537 String spKey = LauncherAppState.getSharedPreferencesKey(); 538 SharedPreferences sp = mContext.getSharedPreferences(spKey, Context.MODE_PRIVATE); 539 SharedPreferences.Editor editor = sp.edit(); 540 editor.putBoolean(UPGRADED_FROM_OLD_DATABASE, true); 541 editor.putBoolean(EMPTY_DATABASE_CREATED, false); 542 editor.commit(); 543 } 544 545 private void setFlagEmptyDbCreated() { 546 String spKey = LauncherAppState.getSharedPreferencesKey(); 547 SharedPreferences sp = mContext.getSharedPreferences(spKey, Context.MODE_PRIVATE); 548 SharedPreferences.Editor editor = sp.edit(); 549 editor.putBoolean(EMPTY_DATABASE_CREATED, true); 550 editor.putBoolean(UPGRADED_FROM_OLD_DATABASE, false); 551 editor.commit(); 552 } 553 554 // We rearrange the screens from the old launcher 555 // 12345 -> 34512 556 private long upgradeLauncherDb_permuteScreens(long screen) { 557 if (screen >= 2) { 558 return screen - 2; 559 } else { 560 return screen + 3; 561 } 562 } 563 564 private boolean convertDatabase(SQLiteDatabase db, Uri uri, 565 ContentValuesCallback cb, boolean deleteRows) { 566 if (LOGD) Log.d(TAG, "converting database from an older format, but not onUpgrade"); 567 boolean converted = false; 568 569 final ContentResolver resolver = mContext.getContentResolver(); 570 Cursor cursor = null; 571 572 try { 573 cursor = resolver.query(uri, null, null, null, null); 574 } catch (Exception e) { 575 // Ignore 576 } 577 578 // We already have a favorites database in the old provider 579 if (cursor != null) { 580 try { 581 if (cursor.getCount() > 0) { 582 converted = copyFromCursor(db, cursor, cb) > 0; 583 if (converted && deleteRows) { 584 resolver.delete(uri, null, null); 585 } 586 } 587 } finally { 588 cursor.close(); 589 } 590 } 591 592 if (converted) { 593 // Convert widgets from this import into widgets 594 if (LOGD) Log.d(TAG, "converted and now triggering widget upgrade"); 595 convertWidgets(db); 596 597 // Update max item id 598 mMaxItemId = initializeMaxItemId(db); 599 if (LOGD) Log.d(TAG, "mMaxItemId: " + mMaxItemId); 600 } 601 602 return converted; 603 } 604 605 private int copyFromCursor(SQLiteDatabase db, Cursor c, ContentValuesCallback cb) { 606 final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID); 607 final int intentIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.INTENT); 608 final int titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE); 609 final int iconTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_TYPE); 610 final int iconIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON); 611 final int iconPackageIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_PACKAGE); 612 final int iconResourceIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_RESOURCE); 613 final int containerIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER); 614 final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE); 615 final int screenIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN); 616 final int cellXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX); 617 final int cellYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY); 618 final int uriIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.URI); 619 final int displayModeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.DISPLAY_MODE); 620 621 ContentValues[] rows = new ContentValues[c.getCount()]; 622 int i = 0; 623 while (c.moveToNext()) { 624 ContentValues values = new ContentValues(c.getColumnCount()); 625 values.put(LauncherSettings.Favorites._ID, c.getLong(idIndex)); 626 values.put(LauncherSettings.Favorites.INTENT, c.getString(intentIndex)); 627 values.put(LauncherSettings.Favorites.TITLE, c.getString(titleIndex)); 628 values.put(LauncherSettings.Favorites.ICON_TYPE, c.getInt(iconTypeIndex)); 629 values.put(LauncherSettings.Favorites.ICON, c.getBlob(iconIndex)); 630 values.put(LauncherSettings.Favorites.ICON_PACKAGE, c.getString(iconPackageIndex)); 631 values.put(LauncherSettings.Favorites.ICON_RESOURCE, c.getString(iconResourceIndex)); 632 values.put(LauncherSettings.Favorites.CONTAINER, c.getInt(containerIndex)); 633 values.put(LauncherSettings.Favorites.ITEM_TYPE, c.getInt(itemTypeIndex)); 634 values.put(LauncherSettings.Favorites.APPWIDGET_ID, -1); 635 values.put(LauncherSettings.Favorites.SCREEN, c.getInt(screenIndex)); 636 values.put(LauncherSettings.Favorites.CELLX, c.getInt(cellXIndex)); 637 values.put(LauncherSettings.Favorites.CELLY, c.getInt(cellYIndex)); 638 values.put(LauncherSettings.Favorites.URI, c.getString(uriIndex)); 639 values.put(LauncherSettings.Favorites.DISPLAY_MODE, c.getInt(displayModeIndex)); 640 if (cb != null) { 641 cb.onRow(values); 642 } 643 rows[i++] = values; 644 } 645 646 int total = 0; 647 if (i > 0) { 648 db.beginTransaction(); 649 try { 650 int numValues = rows.length; 651 for (i = 0; i < numValues; i++) { 652 if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, rows[i]) < 0) { 653 return 0; 654 } else { 655 total++; 656 } 657 } 658 db.setTransactionSuccessful(); 659 } finally { 660 db.endTransaction(); 661 } 662 } 663 664 return total; 665 } 666 667 @Override 668 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 669 if (LOGD) Log.d(TAG, "onUpgrade triggered: " + oldVersion); 670 671 int version = oldVersion; 672 if (version < 3) { 673 // upgrade 1,2 -> 3 added appWidgetId column 674 db.beginTransaction(); 675 try { 676 // Insert new column for holding appWidgetIds 677 db.execSQL("ALTER TABLE favorites " + 678 "ADD COLUMN appWidgetId INTEGER NOT NULL DEFAULT -1;"); 679 db.setTransactionSuccessful(); 680 version = 3; 681 } catch (SQLException ex) { 682 // Old version remains, which means we wipe old data 683 Log.e(TAG, ex.getMessage(), ex); 684 } finally { 685 db.endTransaction(); 686 } 687 688 // Convert existing widgets only if table upgrade was successful 689 if (version == 3) { 690 convertWidgets(db); 691 } 692 } 693 694 if (version < 4) { 695 version = 4; 696 } 697 698 // Where's version 5? 699 // - Donut and sholes on 2.0 shipped with version 4 of launcher1. 700 // - Passion shipped on 2.1 with version 6 of launcher3 701 // - Sholes shipped on 2.1r1 (aka Mr. 3) with version 5 of launcher 1 702 // but version 5 on there was the updateContactsShortcuts change 703 // which was version 6 in launcher 2 (first shipped on passion 2.1r1). 704 // The updateContactsShortcuts change is idempotent, so running it twice 705 // is okay so we'll do that when upgrading the devices that shipped with it. 706 if (version < 6) { 707 // We went from 3 to 5 screens. Move everything 1 to the right 708 db.beginTransaction(); 709 try { 710 db.execSQL("UPDATE favorites SET screen=(screen + 1);"); 711 db.setTransactionSuccessful(); 712 } catch (SQLException ex) { 713 // Old version remains, which means we wipe old data 714 Log.e(TAG, ex.getMessage(), ex); 715 } finally { 716 db.endTransaction(); 717 } 718 719 // We added the fast track. 720 if (updateContactsShortcuts(db)) { 721 version = 6; 722 } 723 } 724 725 if (version < 7) { 726 // Version 7 gets rid of the special search widget. 727 convertWidgets(db); 728 version = 7; 729 } 730 731 if (version < 8) { 732 // Version 8 (froyo) has the icons all normalized. This should 733 // already be the case in practice, but we now rely on it and don't 734 // resample the images each time. 735 normalizeIcons(db); 736 version = 8; 737 } 738 739 if (version < 9) { 740 // The max id is not yet set at this point (onUpgrade is triggered in the ctor 741 // before it gets a change to get set, so we need to read it here when we use it) 742 if (mMaxItemId == -1) { 743 mMaxItemId = initializeMaxItemId(db); 744 } 745 746 // Add default hotseat icons 747 loadFavorites(db, new DefaultLayoutParser(mContext, mAppWidgetHost, this, 748 mContext.getResources(), R.xml.update_workspace)); 749 version = 9; 750 } 751 752 // We bumped the version three time during JB, once to update the launch flags, once to 753 // update the override for the default launch animation and once to set the mimetype 754 // to improve startup performance 755 if (version < 12) { 756 // Contact shortcuts need a different set of flags to be launched now 757 // The updateContactsShortcuts change is idempotent, so we can keep using it like 758 // back in the Donut days 759 updateContactsShortcuts(db); 760 version = 12; 761 } 762 763 if (version < 13) { 764 // With the new shrink-wrapped and re-orderable workspaces, it makes sense 765 // to persist workspace screens and their relative order. 766 mMaxScreenId = 0; 767 768 // This will never happen in the wild, but when we switch to using workspace 769 // screen ids, redo the import from old launcher. 770 sJustLoadedFromOldDb = true; 771 772 addWorkspacesTable(db); 773 version = 13; 774 } 775 776 if (version < 14) { 777 db.beginTransaction(); 778 try { 779 // Insert new column for holding widget provider name 780 db.execSQL("ALTER TABLE favorites " + 781 "ADD COLUMN appWidgetProvider TEXT;"); 782 db.setTransactionSuccessful(); 783 version = 14; 784 } catch (SQLException ex) { 785 // Old version remains, which means we wipe old data 786 Log.e(TAG, ex.getMessage(), ex); 787 } finally { 788 db.endTransaction(); 789 } 790 } 791 792 if (version < 15) { 793 db.beginTransaction(); 794 try { 795 // Insert new column for holding update timestamp 796 db.execSQL("ALTER TABLE favorites " + 797 "ADD COLUMN modified INTEGER NOT NULL DEFAULT 0;"); 798 db.execSQL("ALTER TABLE workspaceScreens " + 799 "ADD COLUMN modified INTEGER NOT NULL DEFAULT 0;"); 800 db.setTransactionSuccessful(); 801 version = 15; 802 } catch (SQLException ex) { 803 // Old version remains, which means we wipe old data 804 Log.e(TAG, ex.getMessage(), ex); 805 } finally { 806 db.endTransaction(); 807 } 808 } 809 810 811 if (version < 16) { 812 db.beginTransaction(); 813 try { 814 // Insert new column for holding restore status 815 db.execSQL("ALTER TABLE favorites " + 816 "ADD COLUMN restored INTEGER NOT NULL DEFAULT 0;"); 817 db.setTransactionSuccessful(); 818 version = 16; 819 } catch (SQLException ex) { 820 // Old version remains, which means we wipe old data 821 Log.e(TAG, ex.getMessage(), ex); 822 } finally { 823 db.endTransaction(); 824 } 825 } 826 827 if (version < 17) { 828 // We use the db version upgrade here to identify users who may not have seen 829 // clings yet (because they weren't available), but for whom the clings are now 830 // available (tablet users). Because one of the possible cling flows (migration) 831 // is very destructive (wipes out workspaces), we want to prevent this from showing 832 // until clear data. We do so by marking that the clings have been shown. 833 LauncherClings.synchonouslyMarkFirstRunClingDismissed(mContext); 834 version = 17; 835 } 836 837 if (version < 18) { 838 // No-op 839 version = 18; 840 } 841 842 if (version < 19) { 843 // Due to a data loss bug, some users may have items associated with screen ids 844 // which no longer exist. Since this can cause other problems, and since the user 845 // will never see these items anyway, we use database upgrade as an opportunity to 846 // clean things up. 847 removeOrphanedItems(db); 848 version = 19; 849 } 850 851 if (version < 20) { 852 // Add userId column 853 if (addProfileColumn(db)) { 854 version = 20; 855 } 856 // else old version remains, which means we wipe old data 857 } 858 859 if (version != DATABASE_VERSION) { 860 Log.w(TAG, "Destroying all old data."); 861 db.execSQL("DROP TABLE IF EXISTS " + TABLE_FAVORITES); 862 db.execSQL("DROP TABLE IF EXISTS " + TABLE_WORKSPACE_SCREENS); 863 864 onCreate(db); 865 } 866 } 867 868 @Override 869 public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) { 870 // This shouldn't happen -- throw our hands up in the air and start over. 871 Log.w(TAG, "Database version downgrade from: " + oldVersion + " to " + newVersion + 872 ". Wiping databse."); 873 createEmptyDB(db); 874 } 875 876 877 /** 878 * Clears all the data for a fresh start. 879 */ 880 public void createEmptyDB(SQLiteDatabase db) { 881 db.execSQL("DROP TABLE IF EXISTS " + TABLE_FAVORITES); 882 db.execSQL("DROP TABLE IF EXISTS " + TABLE_WORKSPACE_SCREENS); 883 onCreate(db); 884 } 885 886 private boolean addProfileColumn(SQLiteDatabase db) { 887 db.beginTransaction(); 888 try { 889 UserManagerCompat userManager = UserManagerCompat.getInstance(mContext); 890 // Default to the serial number of this user, for older 891 // shortcuts. 892 long userSerialNumber = userManager.getSerialNumberForUser( 893 UserHandleCompat.myUserHandle()); 894 // Insert new column for holding user serial number 895 db.execSQL("ALTER TABLE favorites " + 896 "ADD COLUMN profileId INTEGER DEFAULT " 897 + userSerialNumber + ";"); 898 db.setTransactionSuccessful(); 899 } catch (SQLException ex) { 900 // Old version remains, which means we wipe old data 901 Log.e(TAG, ex.getMessage(), ex); 902 return false; 903 } finally { 904 db.endTransaction(); 905 } 906 return true; 907 } 908 909 private boolean updateContactsShortcuts(SQLiteDatabase db) { 910 final String selectWhere = buildOrWhereString(Favorites.ITEM_TYPE, 911 new int[] { Favorites.ITEM_TYPE_SHORTCUT }); 912 913 Cursor c = null; 914 final String actionQuickContact = "com.android.contacts.action.QUICK_CONTACT"; 915 db.beginTransaction(); 916 try { 917 // Select and iterate through each matching widget 918 c = db.query(TABLE_FAVORITES, 919 new String[] { Favorites._ID, Favorites.INTENT }, 920 selectWhere, null, null, null, null); 921 if (c == null) return false; 922 923 if (LOGD) Log.d(TAG, "found upgrade cursor count=" + c.getCount()); 924 925 final int idIndex = c.getColumnIndex(Favorites._ID); 926 final int intentIndex = c.getColumnIndex(Favorites.INTENT); 927 928 while (c.moveToNext()) { 929 long favoriteId = c.getLong(idIndex); 930 final String intentUri = c.getString(intentIndex); 931 if (intentUri != null) { 932 try { 933 final Intent intent = Intent.parseUri(intentUri, 0); 934 android.util.Log.d("Home", intent.toString()); 935 final Uri uri = intent.getData(); 936 if (uri != null) { 937 final String data = uri.toString(); 938 if ((Intent.ACTION_VIEW.equals(intent.getAction()) || 939 actionQuickContact.equals(intent.getAction())) && 940 (data.startsWith("content://contacts/people/") || 941 data.startsWith("content://com.android.contacts/" + 942 "contacts/lookup/"))) { 943 944 final Intent newIntent = new Intent(actionQuickContact); 945 // When starting from the launcher, start in a new, cleared task 946 // CLEAR_WHEN_TASK_RESET cannot reset the root of a task, so we 947 // clear the whole thing preemptively here since 948 // QuickContactActivity will finish itself when launching other 949 // detail activities. 950 newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | 951 Intent.FLAG_ACTIVITY_CLEAR_TASK); 952 newIntent.putExtra( 953 Launcher.INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION, true); 954 newIntent.setData(uri); 955 // Determine the type and also put that in the shortcut 956 // (that can speed up launch a bit) 957 newIntent.setDataAndType(uri, newIntent.resolveType(mContext)); 958 959 final ContentValues values = new ContentValues(); 960 values.put(LauncherSettings.Favorites.INTENT, 961 newIntent.toUri(0)); 962 963 String updateWhere = Favorites._ID + "=" + favoriteId; 964 db.update(TABLE_FAVORITES, values, updateWhere, null); 965 } 966 } 967 } catch (RuntimeException ex) { 968 Log.e(TAG, "Problem upgrading shortcut", ex); 969 } catch (URISyntaxException e) { 970 Log.e(TAG, "Problem upgrading shortcut", e); 971 } 972 } 973 } 974 975 db.setTransactionSuccessful(); 976 } catch (SQLException ex) { 977 Log.w(TAG, "Problem while upgrading contacts", ex); 978 return false; 979 } finally { 980 db.endTransaction(); 981 if (c != null) { 982 c.close(); 983 } 984 } 985 986 return true; 987 } 988 989 private void normalizeIcons(SQLiteDatabase db) { 990 Log.d(TAG, "normalizing icons"); 991 992 db.beginTransaction(); 993 Cursor c = null; 994 SQLiteStatement update = null; 995 try { 996 boolean logged = false; 997 update = db.compileStatement("UPDATE favorites " 998 + "SET icon=? WHERE _id=?"); 999 1000 c = db.rawQuery("SELECT _id, icon FROM favorites WHERE iconType=" + 1001 Favorites.ICON_TYPE_BITMAP, null); 1002 1003 final int idIndex = c.getColumnIndexOrThrow(Favorites._ID); 1004 final int iconIndex = c.getColumnIndexOrThrow(Favorites.ICON); 1005 1006 while (c.moveToNext()) { 1007 long id = c.getLong(idIndex); 1008 byte[] data = c.getBlob(iconIndex); 1009 try { 1010 Bitmap bitmap = Utilities.createIconBitmap( 1011 BitmapFactory.decodeByteArray(data, 0, data.length), 1012 mContext); 1013 if (bitmap != null) { 1014 update.bindLong(1, id); 1015 data = ItemInfo.flattenBitmap(bitmap); 1016 if (data != null) { 1017 update.bindBlob(2, data); 1018 update.execute(); 1019 } 1020 bitmap.recycle(); 1021 } 1022 } catch (Exception e) { 1023 if (!logged) { 1024 Log.e(TAG, "Failed normalizing icon " + id, e); 1025 } else { 1026 Log.e(TAG, "Also failed normalizing icon " + id); 1027 } 1028 logged = true; 1029 } 1030 } 1031 db.setTransactionSuccessful(); 1032 } catch (SQLException ex) { 1033 Log.w(TAG, "Problem while allocating appWidgetIds for existing widgets", ex); 1034 } finally { 1035 db.endTransaction(); 1036 if (update != null) { 1037 update.close(); 1038 } 1039 if (c != null) { 1040 c.close(); 1041 } 1042 } 1043 } 1044 1045 // Generates a new ID to use for an object in your database. This method should be only 1046 // called from the main UI thread. As an exception, we do call it when we call the 1047 // constructor from the worker thread; however, this doesn't extend until after the 1048 // constructor is called, and we only pass a reference to LauncherProvider to LauncherApp 1049 // after that point 1050 @Override 1051 public long generateNewItemId() { 1052 if (mMaxItemId < 0) { 1053 throw new RuntimeException("Error: max item id was not initialized"); 1054 } 1055 mMaxItemId += 1; 1056 return mMaxItemId; 1057 } 1058 1059 @Override 1060 public long insertAndCheck(SQLiteDatabase db, ContentValues values) { 1061 return dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values); 1062 } 1063 1064 public void updateMaxItemId(long id) { 1065 mMaxItemId = id + 1; 1066 } 1067 1068 public void checkId(String table, ContentValues values) { 1069 long id = values.getAsLong(LauncherSettings.BaseLauncherColumns._ID); 1070 if (table == LauncherProvider.TABLE_WORKSPACE_SCREENS) { 1071 mMaxScreenId = Math.max(id, mMaxScreenId); 1072 } else { 1073 mMaxItemId = Math.max(id, mMaxItemId); 1074 } 1075 } 1076 1077 private long initializeMaxItemId(SQLiteDatabase db) { 1078 Cursor c = db.rawQuery("SELECT MAX(_id) FROM favorites", null); 1079 1080 // get the result 1081 final int maxIdIndex = 0; 1082 long id = -1; 1083 if (c != null && c.moveToNext()) { 1084 id = c.getLong(maxIdIndex); 1085 } 1086 if (c != null) { 1087 c.close(); 1088 } 1089 1090 if (id == -1) { 1091 throw new RuntimeException("Error: could not query max item id"); 1092 } 1093 1094 return id; 1095 } 1096 1097 // Generates a new ID to use for an workspace screen in your database. This method 1098 // should be only called from the main UI thread. As an exception, we do call it when we 1099 // call the constructor from the worker thread; however, this doesn't extend until after the 1100 // constructor is called, and we only pass a reference to LauncherProvider to LauncherApp 1101 // after that point 1102 public long generateNewScreenId() { 1103 if (mMaxScreenId < 0) { 1104 throw new RuntimeException("Error: max screen id was not initialized"); 1105 } 1106 mMaxScreenId += 1; 1107 // Log to disk 1108 Launcher.addDumpLog(TAG, "11683562 - generateNewScreenId(): " + mMaxScreenId, true); 1109 return mMaxScreenId; 1110 } 1111 1112 public void updateMaxScreenId(long maxScreenId) { 1113 // Log to disk 1114 Launcher.addDumpLog(TAG, "11683562 - updateMaxScreenId(): " + maxScreenId, true); 1115 mMaxScreenId = maxScreenId; 1116 } 1117 1118 private long initializeMaxScreenId(SQLiteDatabase db) { 1119 Cursor c = db.rawQuery("SELECT MAX(" + LauncherSettings.WorkspaceScreens._ID + ") FROM " + TABLE_WORKSPACE_SCREENS, null); 1120 1121 // get the result 1122 final int maxIdIndex = 0; 1123 long id = -1; 1124 if (c != null && c.moveToNext()) { 1125 id = c.getLong(maxIdIndex); 1126 } 1127 if (c != null) { 1128 c.close(); 1129 } 1130 1131 if (id == -1) { 1132 throw new RuntimeException("Error: could not query max screen id"); 1133 } 1134 1135 // Log to disk 1136 Launcher.addDumpLog(TAG, "11683562 - initializeMaxScreenId(): " + id, true); 1137 return id; 1138 } 1139 1140 /** 1141 * Upgrade existing clock and photo frame widgets into their new widget 1142 * equivalents. 1143 */ 1144 private void convertWidgets(SQLiteDatabase db) { 1145 final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext); 1146 final int[] bindSources = new int[] { 1147 Favorites.ITEM_TYPE_WIDGET_CLOCK, 1148 Favorites.ITEM_TYPE_WIDGET_PHOTO_FRAME, 1149 Favorites.ITEM_TYPE_WIDGET_SEARCH, 1150 }; 1151 1152 final String selectWhere = buildOrWhereString(Favorites.ITEM_TYPE, bindSources); 1153 1154 Cursor c = null; 1155 1156 db.beginTransaction(); 1157 try { 1158 // Select and iterate through each matching widget 1159 c = db.query(TABLE_FAVORITES, new String[] { Favorites._ID, Favorites.ITEM_TYPE }, 1160 selectWhere, null, null, null, null); 1161 1162 if (LOGD) Log.d(TAG, "found upgrade cursor count=" + c.getCount()); 1163 1164 final ContentValues values = new ContentValues(); 1165 while (c != null && c.moveToNext()) { 1166 long favoriteId = c.getLong(0); 1167 int favoriteType = c.getInt(1); 1168 1169 // Allocate and update database with new appWidgetId 1170 try { 1171 int appWidgetId = mAppWidgetHost.allocateAppWidgetId(); 1172 1173 if (LOGD) { 1174 Log.d(TAG, "allocated appWidgetId=" + appWidgetId 1175 + " for favoriteId=" + favoriteId); 1176 } 1177 values.clear(); 1178 values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_APPWIDGET); 1179 values.put(Favorites.APPWIDGET_ID, appWidgetId); 1180 1181 // Original widgets might not have valid spans when upgrading 1182 if (favoriteType == Favorites.ITEM_TYPE_WIDGET_SEARCH) { 1183 values.put(LauncherSettings.Favorites.SPANX, 4); 1184 values.put(LauncherSettings.Favorites.SPANY, 1); 1185 } else { 1186 values.put(LauncherSettings.Favorites.SPANX, 2); 1187 values.put(LauncherSettings.Favorites.SPANY, 2); 1188 } 1189 1190 String updateWhere = Favorites._ID + "=" + favoriteId; 1191 db.update(TABLE_FAVORITES, values, updateWhere, null); 1192 1193 if (favoriteType == Favorites.ITEM_TYPE_WIDGET_CLOCK) { 1194 // TODO: check return value 1195 appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, 1196 new ComponentName("com.android.alarmclock", 1197 "com.android.alarmclock.AnalogAppWidgetProvider")); 1198 } else if (favoriteType == Favorites.ITEM_TYPE_WIDGET_PHOTO_FRAME) { 1199 // TODO: check return value 1200 appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, 1201 new ComponentName("com.android.camera", 1202 "com.android.camera.PhotoAppWidgetProvider")); 1203 } else if (favoriteType == Favorites.ITEM_TYPE_WIDGET_SEARCH) { 1204 // TODO: check return value 1205 appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, 1206 getSearchWidgetProvider()); 1207 } 1208 } catch (RuntimeException ex) { 1209 Log.e(TAG, "Problem allocating appWidgetId", ex); 1210 } 1211 } 1212 1213 db.setTransactionSuccessful(); 1214 } catch (SQLException ex) { 1215 Log.w(TAG, "Problem while allocating appWidgetIds for existing widgets", ex); 1216 } finally { 1217 db.endTransaction(); 1218 if (c != null) { 1219 c.close(); 1220 } 1221 } 1222 1223 // Update max item id 1224 mMaxItemId = initializeMaxItemId(db); 1225 if (LOGD) Log.d(TAG, "mMaxItemId: " + mMaxItemId); 1226 } 1227 1228 private boolean initializeExternalAdd(ContentValues values) { 1229 // 1. Ensure that externally added items have a valid item id 1230 long id = generateNewItemId(); 1231 values.put(LauncherSettings.Favorites._ID, id); 1232 1233 // 2. In the case of an app widget, and if no app widget id is specified, we 1234 // attempt allocate and bind the widget. 1235 Integer itemType = values.getAsInteger(LauncherSettings.Favorites.ITEM_TYPE); 1236 if (itemType != null && 1237 itemType.intValue() == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET && 1238 !values.containsKey(LauncherSettings.Favorites.APPWIDGET_ID)) { 1239 1240 final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext); 1241 ComponentName cn = ComponentName.unflattenFromString( 1242 values.getAsString(Favorites.APPWIDGET_PROVIDER)); 1243 1244 if (cn != null) { 1245 try { 1246 int appWidgetId = mAppWidgetHost.allocateAppWidgetId(); 1247 values.put(LauncherSettings.Favorites.APPWIDGET_ID, appWidgetId); 1248 if (!appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId,cn)) { 1249 return false; 1250 } 1251 } catch (RuntimeException e) { 1252 Log.e(TAG, "Failed to initialize external widget", e); 1253 return false; 1254 } 1255 } else { 1256 return false; 1257 } 1258 } 1259 1260 // Add screen id if not present 1261 long screenId = values.getAsLong(LauncherSettings.Favorites.SCREEN); 1262 if (!addScreenIdIfNecessary(screenId)) { 1263 return false; 1264 } 1265 return true; 1266 } 1267 1268 // Returns true of screen id exists, or if successfully added 1269 private boolean addScreenIdIfNecessary(long screenId) { 1270 if (!hasScreenId(screenId)) { 1271 int rank = getMaxScreenRank() + 1; 1272 1273 ContentValues v = new ContentValues(); 1274 v.put(LauncherSettings.WorkspaceScreens._ID, screenId); 1275 v.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, rank); 1276 if (dbInsertAndCheck(this, getWritableDatabase(), 1277 TABLE_WORKSPACE_SCREENS, null, v) < 0) { 1278 return false; 1279 } 1280 } 1281 return true; 1282 } 1283 1284 private boolean hasScreenId(long screenId) { 1285 SQLiteDatabase db = getWritableDatabase(); 1286 Cursor c = db.rawQuery("SELECT * FROM " + TABLE_WORKSPACE_SCREENS + " WHERE " 1287 + LauncherSettings.WorkspaceScreens._ID + " = " + screenId, null); 1288 if (c != null) { 1289 int count = c.getCount(); 1290 c.close(); 1291 return count > 0; 1292 } else { 1293 return false; 1294 } 1295 } 1296 1297 private int getMaxScreenRank() { 1298 SQLiteDatabase db = getWritableDatabase(); 1299 Cursor c = db.rawQuery("SELECT MAX(" + LauncherSettings.WorkspaceScreens.SCREEN_RANK 1300 + ") FROM " + TABLE_WORKSPACE_SCREENS, null); 1301 1302 // get the result 1303 final int maxRankIndex = 0; 1304 int rank = -1; 1305 if (c != null && c.moveToNext()) { 1306 rank = c.getInt(maxRankIndex); 1307 } 1308 if (c != null) { 1309 c.close(); 1310 } 1311 1312 return rank; 1313 } 1314 1315 private int loadFavorites(SQLiteDatabase db, AutoInstallsLayout loader) { 1316 ArrayList<Long> screenIds = new ArrayList<Long>(); 1317 // TODO: Use multiple loaders with fall-back and transaction. 1318 int count = loader.loadLayout(db, screenIds); 1319 1320 // Add the screens specified by the items above 1321 Collections.sort(screenIds); 1322 int rank = 0; 1323 ContentValues values = new ContentValues(); 1324 for (Long id : screenIds) { 1325 values.clear(); 1326 values.put(LauncherSettings.WorkspaceScreens._ID, id); 1327 values.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, rank); 1328 if (dbInsertAndCheck(this, db, TABLE_WORKSPACE_SCREENS, null, values) < 0) { 1329 throw new RuntimeException("Failed initialize screen table" 1330 + "from default layout"); 1331 } 1332 rank++; 1333 } 1334 1335 // Ensure that the max ids are initialized 1336 mMaxItemId = initializeMaxItemId(db); 1337 mMaxScreenId = initializeMaxScreenId(db); 1338 1339 return count; 1340 } 1341 1342 private ComponentName getSearchWidgetProvider() { 1343 AppWidgetProviderInfo searchProvider = Utilities.getSearchWidgetProvider(mContext); 1344 return (searchProvider == null) ? null : searchProvider.provider; 1345 } 1346 1347 private void migrateLauncher2Shortcuts(SQLiteDatabase db, Uri uri) { 1348 final ContentResolver resolver = mContext.getContentResolver(); 1349 Cursor c = null; 1350 int count = 0; 1351 int curScreen = 0; 1352 1353 try { 1354 c = resolver.query(uri, null, null, null, "title ASC"); 1355 } catch (Exception e) { 1356 // Ignore 1357 } 1358 1359 // We already have a favorites database in the old provider 1360 if (c != null) { 1361 try { 1362 if (c.getCount() > 0) { 1363 final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID); 1364 final int intentIndex 1365 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.INTENT); 1366 final int titleIndex 1367 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE); 1368 final int iconTypeIndex 1369 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_TYPE); 1370 final int iconIndex 1371 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON); 1372 final int iconPackageIndex 1373 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_PACKAGE); 1374 final int iconResourceIndex 1375 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_RESOURCE); 1376 final int containerIndex 1377 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER); 1378 final int itemTypeIndex 1379 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE); 1380 final int screenIndex 1381 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN); 1382 final int cellXIndex 1383 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX); 1384 final int cellYIndex 1385 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY); 1386 final int uriIndex 1387 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.URI); 1388 final int displayModeIndex 1389 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.DISPLAY_MODE); 1390 final int profileIndex 1391 = c.getColumnIndex(LauncherSettings.Favorites.PROFILE_ID); 1392 1393 int i = 0; 1394 int curX = 0; 1395 int curY = 0; 1396 1397 final LauncherAppState app = LauncherAppState.getInstance(); 1398 final DeviceProfile grid = app.getDynamicGrid().getDeviceProfile(); 1399 final int width = (int) grid.numColumns; 1400 final int height = (int) grid.numRows; 1401 final int hotseatWidth = (int) grid.numHotseatIcons; 1402 1403 final HashSet<String> seenIntents = new HashSet<String>(c.getCount()); 1404 1405 final ArrayList<ContentValues> shortcuts = new ArrayList<ContentValues>(); 1406 final ArrayList<ContentValues> folders = new ArrayList<ContentValues>(); 1407 final SparseArray<ContentValues> hotseat = new SparseArray<ContentValues>(); 1408 1409 while (c.moveToNext()) { 1410 final int itemType = c.getInt(itemTypeIndex); 1411 if (itemType != Favorites.ITEM_TYPE_APPLICATION 1412 && itemType != Favorites.ITEM_TYPE_SHORTCUT 1413 && itemType != Favorites.ITEM_TYPE_FOLDER) { 1414 continue; 1415 } 1416 1417 final int cellX = c.getInt(cellXIndex); 1418 final int cellY = c.getInt(cellYIndex); 1419 final int screen = c.getInt(screenIndex); 1420 int container = c.getInt(containerIndex); 1421 final String intentStr = c.getString(intentIndex); 1422 1423 UserManagerCompat userManager = UserManagerCompat.getInstance(mContext); 1424 UserHandleCompat userHandle; 1425 final long userSerialNumber; 1426 if (profileIndex != -1 && !c.isNull(profileIndex)) { 1427 userSerialNumber = c.getInt(profileIndex); 1428 userHandle = userManager.getUserForSerialNumber(userSerialNumber); 1429 } else { 1430 // Default to the serial number of this user, for older 1431 // shortcuts. 1432 userHandle = UserHandleCompat.myUserHandle(); 1433 userSerialNumber = userManager.getSerialNumberForUser(userHandle); 1434 } 1435 1436 if (userHandle == null) { 1437 Launcher.addDumpLog(TAG, "skipping deleted user", true); 1438 continue; 1439 } 1440 1441 Launcher.addDumpLog(TAG, "migrating \"" 1442 + c.getString(titleIndex) + "\" (" 1443 + cellX + "," + cellY + "@" 1444 + LauncherSettings.Favorites.containerToString(container) 1445 + "/" + screen 1446 + "): " + intentStr, true); 1447 1448 if (itemType != Favorites.ITEM_TYPE_FOLDER) { 1449 1450 final Intent intent; 1451 final ComponentName cn; 1452 try { 1453 intent = Intent.parseUri(intentStr, 0); 1454 } catch (URISyntaxException e) { 1455 // bogus intent? 1456 Launcher.addDumpLog(TAG, 1457 "skipping invalid intent uri", true); 1458 continue; 1459 } 1460 1461 cn = intent.getComponent(); 1462 if (TextUtils.isEmpty(intentStr)) { 1463 // no intent? no icon 1464 Launcher.addDumpLog(TAG, "skipping empty intent", true); 1465 continue; 1466 } else if (cn != null && 1467 !LauncherModel.isValidPackageActivity(mContext, cn, 1468 userHandle)) { 1469 // component no longer exists. 1470 Launcher.addDumpLog(TAG, "skipping item whose component " + 1471 "no longer exists.", true); 1472 continue; 1473 } else if (container == 1474 LauncherSettings.Favorites.CONTAINER_DESKTOP) { 1475 // Dedupe icons directly on the workspace 1476 1477 // Canonicalize 1478 // the Play Store sets the package parameter, but Launcher 1479 // does not, so we clear that out to keep them the same. 1480 // Also ignore intent flags for the purposes of deduping. 1481 intent.setPackage(null); 1482 int flags = intent.getFlags(); 1483 intent.setFlags(0); 1484 final String key = intent.toUri(0); 1485 intent.setFlags(flags); 1486 if (seenIntents.contains(key)) { 1487 Launcher.addDumpLog(TAG, "skipping duplicate", true); 1488 continue; 1489 } else { 1490 seenIntents.add(key); 1491 } 1492 } 1493 } 1494 1495 ContentValues values = new ContentValues(c.getColumnCount()); 1496 values.put(LauncherSettings.Favorites._ID, c.getInt(idIndex)); 1497 values.put(LauncherSettings.Favorites.INTENT, intentStr); 1498 values.put(LauncherSettings.Favorites.TITLE, c.getString(titleIndex)); 1499 values.put(LauncherSettings.Favorites.ICON_TYPE, 1500 c.getInt(iconTypeIndex)); 1501 values.put(LauncherSettings.Favorites.ICON, c.getBlob(iconIndex)); 1502 values.put(LauncherSettings.Favorites.ICON_PACKAGE, 1503 c.getString(iconPackageIndex)); 1504 values.put(LauncherSettings.Favorites.ICON_RESOURCE, 1505 c.getString(iconResourceIndex)); 1506 values.put(LauncherSettings.Favorites.ITEM_TYPE, itemType); 1507 values.put(LauncherSettings.Favorites.APPWIDGET_ID, -1); 1508 values.put(LauncherSettings.Favorites.URI, c.getString(uriIndex)); 1509 values.put(LauncherSettings.Favorites.DISPLAY_MODE, 1510 c.getInt(displayModeIndex)); 1511 values.put(LauncherSettings.Favorites.PROFILE_ID, userSerialNumber); 1512 1513 if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { 1514 hotseat.put(screen, values); 1515 } 1516 1517 if (container != LauncherSettings.Favorites.CONTAINER_DESKTOP) { 1518 // In a folder or in the hotseat, preserve position 1519 values.put(LauncherSettings.Favorites.SCREEN, screen); 1520 values.put(LauncherSettings.Favorites.CELLX, cellX); 1521 values.put(LauncherSettings.Favorites.CELLY, cellY); 1522 } else { 1523 // For items contained directly on one of the workspace screen, 1524 // we'll determine their location (screen, x, y) in a second pass. 1525 } 1526 1527 values.put(LauncherSettings.Favorites.CONTAINER, container); 1528 1529 if (itemType != Favorites.ITEM_TYPE_FOLDER) { 1530 shortcuts.add(values); 1531 } else { 1532 folders.add(values); 1533 } 1534 } 1535 1536 // Now that we have all the hotseat icons, let's go through them left-right 1537 // and assign valid locations for them in the new hotseat 1538 final int N = hotseat.size(); 1539 for (int idx=0; idx<N; idx++) { 1540 int hotseatX = hotseat.keyAt(idx); 1541 ContentValues values = hotseat.valueAt(idx); 1542 1543 if (hotseatX == grid.hotseatAllAppsRank) { 1544 // let's drop this in the next available hole in the hotseat 1545 while (++hotseatX < hotseatWidth) { 1546 if (hotseat.get(hotseatX) == null) { 1547 // found a spot! move it here 1548 values.put(LauncherSettings.Favorites.SCREEN, 1549 hotseatX); 1550 break; 1551 } 1552 } 1553 } 1554 if (hotseatX >= hotseatWidth) { 1555 // no room for you in the hotseat? it's off to the desktop with you 1556 values.put(LauncherSettings.Favorites.CONTAINER, 1557 Favorites.CONTAINER_DESKTOP); 1558 } 1559 } 1560 1561 final ArrayList<ContentValues> allItems = new ArrayList<ContentValues>(); 1562 // Folders first 1563 allItems.addAll(folders); 1564 // Then shortcuts 1565 allItems.addAll(shortcuts); 1566 1567 // Layout all the folders 1568 for (ContentValues values: allItems) { 1569 if (values.getAsInteger(LauncherSettings.Favorites.CONTAINER) != 1570 LauncherSettings.Favorites.CONTAINER_DESKTOP) { 1571 // Hotseat items and folder items have already had their 1572 // location information set. Nothing to be done here. 1573 continue; 1574 } 1575 values.put(LauncherSettings.Favorites.SCREEN, curScreen); 1576 values.put(LauncherSettings.Favorites.CELLX, curX); 1577 values.put(LauncherSettings.Favorites.CELLY, curY); 1578 curX = (curX + 1) % width; 1579 if (curX == 0) { 1580 curY = (curY + 1); 1581 } 1582 // Leave the last row of icons blank on every screen 1583 if (curY == height - 1) { 1584 curScreen = (int) generateNewScreenId(); 1585 curY = 0; 1586 } 1587 } 1588 1589 if (allItems.size() > 0) { 1590 db.beginTransaction(); 1591 try { 1592 for (ContentValues row: allItems) { 1593 if (row == null) continue; 1594 if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, row) 1595 < 0) { 1596 return; 1597 } else { 1598 count++; 1599 } 1600 } 1601 db.setTransactionSuccessful(); 1602 } finally { 1603 db.endTransaction(); 1604 } 1605 } 1606 1607 db.beginTransaction(); 1608 try { 1609 for (i=0; i<=curScreen; i++) { 1610 final ContentValues values = new ContentValues(); 1611 values.put(LauncherSettings.WorkspaceScreens._ID, i); 1612 values.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, i); 1613 if (dbInsertAndCheck(this, db, TABLE_WORKSPACE_SCREENS, null, values) 1614 < 0) { 1615 return; 1616 } 1617 } 1618 db.setTransactionSuccessful(); 1619 } finally { 1620 db.endTransaction(); 1621 } 1622 } 1623 } finally { 1624 c.close(); 1625 } 1626 } 1627 1628 Launcher.addDumpLog(TAG, "migrated " + count + " icons from Launcher2 into " 1629 + (curScreen+1) + " screens", true); 1630 1631 // ensure that new screens are created to hold these icons 1632 setFlagJustLoadedOldDb(); 1633 1634 // Update max IDs; very important since we just grabbed IDs from another database 1635 mMaxItemId = initializeMaxItemId(db); 1636 mMaxScreenId = initializeMaxScreenId(db); 1637 if (LOGD) Log.d(TAG, "mMaxItemId: " + mMaxItemId + " mMaxScreenId: " + mMaxScreenId); 1638 } 1639 } 1640 1641 /** 1642 * Build a query string that will match any row where the column matches 1643 * anything in the values list. 1644 */ 1645 private static String buildOrWhereString(String column, int[] values) { 1646 StringBuilder selectWhere = new StringBuilder(); 1647 for (int i = values.length - 1; i >= 0; i--) { 1648 selectWhere.append(column).append("=").append(values[i]); 1649 if (i > 0) { 1650 selectWhere.append(" OR "); 1651 } 1652 } 1653 return selectWhere.toString(); 1654 } 1655 1656 static class SqlArguments { 1657 public final String table; 1658 public final String where; 1659 public final String[] args; 1660 1661 SqlArguments(Uri url, String where, String[] args) { 1662 if (url.getPathSegments().size() == 1) { 1663 this.table = url.getPathSegments().get(0); 1664 this.where = where; 1665 this.args = args; 1666 } else if (url.getPathSegments().size() != 2) { 1667 throw new IllegalArgumentException("Invalid URI: " + url); 1668 } else if (!TextUtils.isEmpty(where)) { 1669 throw new UnsupportedOperationException("WHERE clause not supported: " + url); 1670 } else { 1671 this.table = url.getPathSegments().get(0); 1672 this.where = "_id=" + ContentUris.parseId(url); 1673 this.args = null; 1674 } 1675 } 1676 1677 SqlArguments(Uri url) { 1678 if (url.getPathSegments().size() == 1) { 1679 table = url.getPathSegments().get(0); 1680 where = null; 1681 args = null; 1682 } else { 1683 throw new IllegalArgumentException("Invalid URI: " + url); 1684 } 1685 } 1686 } 1687 } 1688