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