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