LauncherProvider.java revision c3a804042844dc4733b4bd4b6ac03bf4b2b015b7
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.launcher2; 18 19import com.android.internal.util.XmlUtils; 20import com.android.launcher.R; 21import com.android.launcher2.LauncherSettings.Favorites; 22 23import org.xmlpull.v1.XmlPullParser; 24import org.xmlpull.v1.XmlPullParserException; 25 26import android.app.SearchManager; 27import android.appwidget.AppWidgetHost; 28import android.appwidget.AppWidgetManager; 29import android.appwidget.AppWidgetProviderInfo; 30import android.content.ComponentName; 31import android.content.ContentProvider; 32import android.content.ContentResolver; 33import android.content.ContentUris; 34import android.content.ContentValues; 35import android.content.Context; 36import android.content.Intent; 37import android.content.pm.ActivityInfo; 38import android.content.pm.PackageManager; 39import android.content.res.Resources; 40import android.content.res.TypedArray; 41import android.content.res.XmlResourceParser; 42import android.database.Cursor; 43import android.database.SQLException; 44import android.database.sqlite.SQLiteDatabase; 45import android.database.sqlite.SQLiteOpenHelper; 46import android.database.sqlite.SQLiteQueryBuilder; 47import android.database.sqlite.SQLiteStatement; 48import android.graphics.Bitmap; 49import android.graphics.BitmapFactory; 50import android.net.Uri; 51import android.provider.Settings; 52import android.text.TextUtils; 53import android.util.AttributeSet; 54import android.util.Log; 55import android.util.Xml; 56 57import java.io.IOException; 58import java.net.URISyntaxException; 59import java.util.ArrayList; 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 String DATABASE_NAME = "launcher.db"; 67 68 private static final int DATABASE_VERSION = 10; 69 70 static final String AUTHORITY = "com.android.launcher2.settings"; 71 72 static final String TABLE_FAVORITES = "favorites"; 73 static final String PARAMETER_NOTIFY = "notify"; 74 75 /** 76 * {@link Uri} triggered at any registered {@link android.database.ContentObserver} when 77 * {@link AppWidgetHost#deleteHost()} is called during database creation. 78 * Use this to recall {@link AppWidgetHost#startListening()} if needed. 79 */ 80 static final Uri CONTENT_APPWIDGET_RESET_URI = 81 Uri.parse("content://" + AUTHORITY + "/appWidgetReset"); 82 83 private DatabaseHelper mOpenHelper; 84 85 @Override 86 public boolean onCreate() { 87 mOpenHelper = new DatabaseHelper(getContext()); 88 ((LauncherApplication) getContext()).setLauncherProvider(this); 89 return true; 90 } 91 92 @Override 93 public String getType(Uri uri) { 94 SqlArguments args = new SqlArguments(uri, null, null); 95 if (TextUtils.isEmpty(args.where)) { 96 return "vnd.android.cursor.dir/" + args.table; 97 } else { 98 return "vnd.android.cursor.item/" + args.table; 99 } 100 } 101 102 @Override 103 public Cursor query(Uri uri, String[] projection, String selection, 104 String[] selectionArgs, String sortOrder) { 105 106 SqlArguments args = new SqlArguments(uri, selection, selectionArgs); 107 SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); 108 qb.setTables(args.table); 109 110 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 111 Cursor result = qb.query(db, projection, args.where, args.args, null, null, sortOrder); 112 result.setNotificationUri(getContext().getContentResolver(), uri); 113 114 return result; 115 } 116 117 private static long dbInsertAndCheck(DatabaseHelper helper, 118 SQLiteDatabase db, String table, String nullColumnHack, ContentValues values) { 119 if (!values.containsKey(LauncherSettings.Favorites._ID)) { 120 throw new RuntimeException("Error: attempting to add item without specifying an id"); 121 } 122 return db.insert(table, nullColumnHack, values); 123 } 124 125 private static void deleteId(SQLiteDatabase db, long id) { 126 Uri uri = LauncherSettings.Favorites.getContentUri(id, false); 127 SqlArguments args = new SqlArguments(uri, null, null); 128 db.delete(args.table, args.where, args.args); 129 } 130 131 @Override 132 public Uri insert(Uri uri, ContentValues initialValues) { 133 SqlArguments args = new SqlArguments(uri); 134 135 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 136 final long rowId = dbInsertAndCheck(mOpenHelper, db, args.table, null, initialValues); 137 if (rowId <= 0) return null; 138 139 uri = ContentUris.withAppendedId(uri, rowId); 140 sendNotify(uri); 141 142 return uri; 143 } 144 145 @Override 146 public int bulkInsert(Uri uri, ContentValues[] values) { 147 SqlArguments args = new SqlArguments(uri); 148 149 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 150 db.beginTransaction(); 151 try { 152 int numValues = values.length; 153 for (int i = 0; i < numValues; i++) { 154 if (dbInsertAndCheck(mOpenHelper, db, args.table, null, values[i]) < 0) { 155 return 0; 156 } 157 } 158 db.setTransactionSuccessful(); 159 } finally { 160 db.endTransaction(); 161 } 162 163 sendNotify(uri); 164 return values.length; 165 } 166 167 @Override 168 public int delete(Uri uri, String selection, String[] selectionArgs) { 169 SqlArguments args = new SqlArguments(uri, selection, selectionArgs); 170 171 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 172 int count = db.delete(args.table, args.where, args.args); 173 if (count > 0) sendNotify(uri); 174 175 return count; 176 } 177 178 @Override 179 public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { 180 SqlArguments args = new SqlArguments(uri, selection, selectionArgs); 181 182 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 183 int count = db.update(args.table, values, args.where, args.args); 184 if (count > 0) sendNotify(uri); 185 186 return count; 187 } 188 189 private void sendNotify(Uri uri) { 190 String notify = uri.getQueryParameter(PARAMETER_NOTIFY); 191 if (notify == null || "true".equals(notify)) { 192 getContext().getContentResolver().notifyChange(uri, null); 193 } 194 } 195 196 public long generateNewId() { 197 return mOpenHelper.generateNewId(); 198 } 199 200 private static class DatabaseHelper extends SQLiteOpenHelper { 201 private static final String TAG_FAVORITES = "favorites"; 202 private static final String TAG_FAVORITE = "favorite"; 203 private static final String TAG_CLOCK = "clock"; 204 private static final String TAG_SEARCH = "search"; 205 private static final String TAG_APPWIDGET = "appwidget"; 206 private static final String TAG_SHORTCUT = "shortcut"; 207 private static final String TAG_FOLDER = "folder"; 208 209 private final Context mContext; 210 private final AppWidgetHost mAppWidgetHost; 211 private long mMaxId = -1; 212 213 DatabaseHelper(Context context) { 214 super(context, DATABASE_NAME, null, DATABASE_VERSION); 215 mContext = context; 216 mAppWidgetHost = new AppWidgetHost(context, Launcher.APPWIDGET_HOST_ID); 217 218 // In the case where neither onCreate nor onUpgrade gets called, we read the maxId from 219 // the DB here 220 if (mMaxId == -1) { 221 mMaxId = initializeMaxId(getWritableDatabase()); 222 } 223 } 224 225 /** 226 * Send notification that we've deleted the {@link AppWidgetHost}, 227 * probably as part of the initial database creation. The receiver may 228 * want to re-call {@link AppWidgetHost#startListening()} to ensure 229 * callbacks are correctly set. 230 */ 231 private void sendAppWidgetResetNotify() { 232 final ContentResolver resolver = mContext.getContentResolver(); 233 resolver.notifyChange(CONTENT_APPWIDGET_RESET_URI, null); 234 } 235 236 @Override 237 public void onCreate(SQLiteDatabase db) { 238 if (LOGD) Log.d(TAG, "creating new launcher database"); 239 240 mMaxId = 1; 241 242 db.execSQL("CREATE TABLE favorites (" + 243 "_id INTEGER PRIMARY KEY," + 244 "title TEXT," + 245 "intent TEXT," + 246 "container INTEGER," + 247 "screen INTEGER," + 248 "cellX INTEGER," + 249 "cellY INTEGER," + 250 "spanX INTEGER," + 251 "spanY INTEGER," + 252 "itemType INTEGER," + 253 "appWidgetId INTEGER NOT NULL DEFAULT -1," + 254 "isShortcut INTEGER," + 255 "iconType INTEGER," + 256 "iconPackage TEXT," + 257 "iconResource TEXT," + 258 "icon BLOB," + 259 "uri TEXT," + 260 "displayMode INTEGER" + 261 ");"); 262 263 // Database was just created, so wipe any previous widgets 264 if (mAppWidgetHost != null) { 265 mAppWidgetHost.deleteHost(); 266 sendAppWidgetResetNotify(); 267 } 268 269 if (!convertDatabase(db)) { 270 // Populate favorites table with initial favorites 271 loadFavorites(db, R.xml.default_workspace); 272 } 273 } 274 275 private boolean convertDatabase(SQLiteDatabase db) { 276 if (LOGD) Log.d(TAG, "converting database from an older format, but not onUpgrade"); 277 boolean converted = false; 278 279 final Uri uri = Uri.parse("content://" + Settings.AUTHORITY + 280 "/old_favorites?notify=true"); 281 final ContentResolver resolver = mContext.getContentResolver(); 282 Cursor cursor = null; 283 284 try { 285 cursor = resolver.query(uri, null, null, null, null); 286 } catch (Exception e) { 287 // Ignore 288 } 289 290 // We already have a favorites database in the old provider 291 if (cursor != null && cursor.getCount() > 0) { 292 try { 293 converted = copyFromCursor(db, cursor) > 0; 294 } finally { 295 cursor.close(); 296 } 297 298 if (converted) { 299 resolver.delete(uri, null, null); 300 } 301 } 302 303 if (converted) { 304 // Convert widgets from this import into widgets 305 if (LOGD) Log.d(TAG, "converted and now triggering widget upgrade"); 306 convertWidgets(db); 307 } 308 309 return converted; 310 } 311 312 private int copyFromCursor(SQLiteDatabase db, Cursor c) { 313 final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID); 314 final int intentIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.INTENT); 315 final int titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE); 316 final int iconTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_TYPE); 317 final int iconIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON); 318 final int iconPackageIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_PACKAGE); 319 final int iconResourceIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_RESOURCE); 320 final int containerIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER); 321 final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE); 322 final int screenIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN); 323 final int cellXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX); 324 final int cellYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY); 325 final int uriIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.URI); 326 final int displayModeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.DISPLAY_MODE); 327 328 ContentValues[] rows = new ContentValues[c.getCount()]; 329 int i = 0; 330 while (c.moveToNext()) { 331 ContentValues values = new ContentValues(c.getColumnCount()); 332 values.put(LauncherSettings.Favorites._ID, c.getLong(idIndex)); 333 values.put(LauncherSettings.Favorites.INTENT, c.getString(intentIndex)); 334 values.put(LauncherSettings.Favorites.TITLE, c.getString(titleIndex)); 335 values.put(LauncherSettings.Favorites.ICON_TYPE, c.getInt(iconTypeIndex)); 336 values.put(LauncherSettings.Favorites.ICON, c.getBlob(iconIndex)); 337 values.put(LauncherSettings.Favorites.ICON_PACKAGE, c.getString(iconPackageIndex)); 338 values.put(LauncherSettings.Favorites.ICON_RESOURCE, c.getString(iconResourceIndex)); 339 values.put(LauncherSettings.Favorites.CONTAINER, c.getInt(containerIndex)); 340 values.put(LauncherSettings.Favorites.ITEM_TYPE, c.getInt(itemTypeIndex)); 341 values.put(LauncherSettings.Favorites.APPWIDGET_ID, -1); 342 values.put(LauncherSettings.Favorites.SCREEN, c.getInt(screenIndex)); 343 values.put(LauncherSettings.Favorites.CELLX, c.getInt(cellXIndex)); 344 values.put(LauncherSettings.Favorites.CELLY, c.getInt(cellYIndex)); 345 values.put(LauncherSettings.Favorites.URI, c.getString(uriIndex)); 346 values.put(LauncherSettings.Favorites.DISPLAY_MODE, c.getInt(displayModeIndex)); 347 rows[i++] = values; 348 } 349 350 db.beginTransaction(); 351 int total = 0; 352 try { 353 int numValues = rows.length; 354 for (i = 0; i < numValues; i++) { 355 if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, rows[i]) < 0) { 356 return 0; 357 } else { 358 total++; 359 } 360 } 361 db.setTransactionSuccessful(); 362 } finally { 363 db.endTransaction(); 364 } 365 366 return total; 367 } 368 369 @Override 370 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 371 if (LOGD) Log.d(TAG, "onUpgrade triggered"); 372 373 int version = oldVersion; 374 if (version < 3) { 375 // upgrade 1,2 -> 3 added appWidgetId column 376 db.beginTransaction(); 377 try { 378 // Insert new column for holding appWidgetIds 379 db.execSQL("ALTER TABLE favorites " + 380 "ADD COLUMN appWidgetId INTEGER NOT NULL DEFAULT -1;"); 381 db.setTransactionSuccessful(); 382 version = 3; 383 } catch (SQLException ex) { 384 // Old version remains, which means we wipe old data 385 Log.e(TAG, ex.getMessage(), ex); 386 } finally { 387 db.endTransaction(); 388 } 389 390 // Convert existing widgets only if table upgrade was successful 391 if (version == 3) { 392 convertWidgets(db); 393 } 394 } 395 396 if (version < 4) { 397 version = 4; 398 } 399 400 // Where's version 5? 401 // - Donut and sholes on 2.0 shipped with version 4 of launcher1. 402 // - Passion shipped on 2.1 with version 6 of launcher2 403 // - Sholes shipped on 2.1r1 (aka Mr. 3) with version 5 of launcher 1 404 // but version 5 on there was the updateContactsShortcuts change 405 // which was version 6 in launcher 2 (first shipped on passion 2.1r1). 406 // The updateContactsShortcuts change is idempotent, so running it twice 407 // is okay so we'll do that when upgrading the devices that shipped with it. 408 if (version < 6) { 409 // We went from 3 to 5 screens. Move everything 1 to the right 410 db.beginTransaction(); 411 try { 412 db.execSQL("UPDATE favorites SET screen=(screen + 1);"); 413 db.setTransactionSuccessful(); 414 } catch (SQLException ex) { 415 // Old version remains, which means we wipe old data 416 Log.e(TAG, ex.getMessage(), ex); 417 } finally { 418 db.endTransaction(); 419 } 420 421 // We added the fast track. 422 if (updateContactsShortcuts(db)) { 423 version = 6; 424 } 425 } 426 427 if (version < 7) { 428 // Version 7 gets rid of the special search widget. 429 convertWidgets(db); 430 version = 7; 431 } 432 433 if (version < 8) { 434 // Version 8 (froyo) has the icons all normalized. This should 435 // already be the case in practice, but we now rely on it and don't 436 // resample the images each time. 437 normalizeIcons(db); 438 version = 8; 439 } 440 441 if (version < 9) { 442 // The max id is not yet set at this point (onUpgrade is triggered in the ctor 443 // before it gets a change to get set, so we need to read it here when we use it) 444 if (mMaxId == -1) { 445 mMaxId = initializeMaxId(db); 446 } 447 448 // Add default hotseat icons 449 loadFavorites(db, R.xml.update_workspace); 450 version = 9; 451 } 452 453 if (version < 10) { 454 // Contact shortcuts need a different set of flags to be launched now 455 // The updateContactsShortcuts change is idempotent, so we can keep using it like 456 // back in the Donut days 457 updateContactsShortcuts(db); 458 version = 10; 459 } 460 461 if (version != DATABASE_VERSION) { 462 Log.w(TAG, "Destroying all old data."); 463 db.execSQL("DROP TABLE IF EXISTS " + TABLE_FAVORITES); 464 onCreate(db); 465 } 466 } 467 468 private boolean updateContactsShortcuts(SQLiteDatabase db) { 469 final String selectWhere = buildOrWhereString(Favorites.ITEM_TYPE, 470 new int[] { Favorites.ITEM_TYPE_SHORTCUT }); 471 472 Cursor c = null; 473 final String actionQuickContact = "com.android.contacts.action.QUICK_CONTACT"; 474 db.beginTransaction(); 475 try { 476 // Select and iterate through each matching widget 477 c = db.query(TABLE_FAVORITES, 478 new String[] { Favorites._ID, Favorites.INTENT }, 479 selectWhere, null, null, null, null); 480 if (c == null) return false; 481 482 if (LOGD) Log.d(TAG, "found upgrade cursor count=" + c.getCount()); 483 484 final int idIndex = c.getColumnIndex(Favorites._ID); 485 final int intentIndex = c.getColumnIndex(Favorites.INTENT); 486 487 while (c.moveToNext()) { 488 long favoriteId = c.getLong(idIndex); 489 final String intentUri = c.getString(intentIndex); 490 if (intentUri != null) { 491 try { 492 final Intent intent = Intent.parseUri(intentUri, 0); 493 android.util.Log.d("Home", intent.toString()); 494 final Uri uri = intent.getData(); 495 if (uri != null) { 496 final String data = uri.toString(); 497 if ((Intent.ACTION_VIEW.equals(intent.getAction()) || 498 actionQuickContact.equals(intent.getAction())) && 499 (data.startsWith("content://contacts/people/") || 500 data.startsWith("content://com.android.contacts/" + 501 "contacts/lookup/"))) { 502 503 final Intent newIntent = new Intent(actionQuickContact); 504 // When starting from the launcher, start in a new, cleared task 505 // CLEAR_WHEN_TASK_RESET cannot reset the root of a task, so we 506 // clear the whole thing preemptively here since 507 // QuickContactActivity will finish itself when launching other 508 // detail activities. 509 newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | 510 Intent.FLAG_ACTIVITY_CLEAR_TASK); 511 512 newIntent.setData(uri); 513 514 final ContentValues values = new ContentValues(); 515 values.put(LauncherSettings.Favorites.INTENT, 516 newIntent.toUri(0)); 517 518 String updateWhere = Favorites._ID + "=" + favoriteId; 519 db.update(TABLE_FAVORITES, values, updateWhere, null); 520 } 521 } 522 } catch (RuntimeException ex) { 523 Log.e(TAG, "Problem upgrading shortcut", ex); 524 } catch (URISyntaxException e) { 525 Log.e(TAG, "Problem upgrading shortcut", e); 526 } 527 } 528 } 529 530 db.setTransactionSuccessful(); 531 } catch (SQLException ex) { 532 Log.w(TAG, "Problem while upgrading contacts", ex); 533 return false; 534 } finally { 535 db.endTransaction(); 536 if (c != null) { 537 c.close(); 538 } 539 } 540 541 return true; 542 } 543 544 private void normalizeIcons(SQLiteDatabase db) { 545 Log.d(TAG, "normalizing icons"); 546 547 db.beginTransaction(); 548 Cursor c = null; 549 SQLiteStatement update = null; 550 try { 551 boolean logged = false; 552 update = db.compileStatement("UPDATE favorites " 553 + "SET icon=? WHERE _id=?"); 554 555 c = db.rawQuery("SELECT _id, icon FROM favorites WHERE iconType=" + 556 Favorites.ICON_TYPE_BITMAP, null); 557 558 final int idIndex = c.getColumnIndexOrThrow(Favorites._ID); 559 final int iconIndex = c.getColumnIndexOrThrow(Favorites.ICON); 560 561 while (c.moveToNext()) { 562 long id = c.getLong(idIndex); 563 byte[] data = c.getBlob(iconIndex); 564 try { 565 Bitmap bitmap = Utilities.resampleIconBitmap( 566 BitmapFactory.decodeByteArray(data, 0, data.length), 567 mContext); 568 if (bitmap != null) { 569 update.bindLong(1, id); 570 data = ItemInfo.flattenBitmap(bitmap); 571 if (data != null) { 572 update.bindBlob(2, data); 573 update.execute(); 574 } 575 bitmap.recycle(); 576 } 577 } catch (Exception e) { 578 if (!logged) { 579 Log.e(TAG, "Failed normalizing icon " + id, e); 580 } else { 581 Log.e(TAG, "Also failed normalizing icon " + id); 582 } 583 logged = true; 584 } 585 } 586 db.setTransactionSuccessful(); 587 } catch (SQLException ex) { 588 Log.w(TAG, "Problem while allocating appWidgetIds for existing widgets", ex); 589 } finally { 590 db.endTransaction(); 591 if (update != null) { 592 update.close(); 593 } 594 if (c != null) { 595 c.close(); 596 } 597 } 598 } 599 600 // Generates a new ID to use for an object in your database. This method should be only 601 // called from the main UI thread. As an exception, we do call it when we call the 602 // constructor from the worker thread; however, this doesn't extend until after the 603 // constructor is called, and we only pass a reference to LauncherProvider to LauncherApp 604 // after that point 605 public long generateNewId() { 606 if (mMaxId < 0) { 607 throw new RuntimeException("Error: max id was not initialized"); 608 } 609 mMaxId += 1; 610 return mMaxId; 611 } 612 613 private long initializeMaxId(SQLiteDatabase db) { 614 Cursor c = db.rawQuery("SELECT MAX(_id) FROM favorites", null); 615 616 // get the result 617 final int maxIdIndex = 0; 618 long id = -1; 619 if (c != null && c.moveToNext()) { 620 id = c.getLong(maxIdIndex); 621 } 622 if (c != null) { 623 c.close(); 624 } 625 626 if (id == -1) { 627 throw new RuntimeException("Error: could not query max id"); 628 } 629 630 return id; 631 } 632 633 /** 634 * Upgrade existing clock and photo frame widgets into their new widget 635 * equivalents. 636 */ 637 private void convertWidgets(SQLiteDatabase db) { 638 final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext); 639 final int[] bindSources = new int[] { 640 Favorites.ITEM_TYPE_WIDGET_CLOCK, 641 Favorites.ITEM_TYPE_WIDGET_PHOTO_FRAME, 642 Favorites.ITEM_TYPE_WIDGET_SEARCH, 643 }; 644 645 final String selectWhere = buildOrWhereString(Favorites.ITEM_TYPE, bindSources); 646 647 Cursor c = null; 648 649 db.beginTransaction(); 650 try { 651 // Select and iterate through each matching widget 652 c = db.query(TABLE_FAVORITES, new String[] { Favorites._ID, Favorites.ITEM_TYPE }, 653 selectWhere, null, null, null, null); 654 655 if (LOGD) Log.d(TAG, "found upgrade cursor count=" + c.getCount()); 656 657 final ContentValues values = new ContentValues(); 658 while (c != null && c.moveToNext()) { 659 long favoriteId = c.getLong(0); 660 int favoriteType = c.getInt(1); 661 662 // Allocate and update database with new appWidgetId 663 try { 664 int appWidgetId = mAppWidgetHost.allocateAppWidgetId(); 665 666 if (LOGD) { 667 Log.d(TAG, "allocated appWidgetId=" + appWidgetId 668 + " for favoriteId=" + favoriteId); 669 } 670 values.clear(); 671 values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_APPWIDGET); 672 values.put(Favorites.APPWIDGET_ID, appWidgetId); 673 674 // Original widgets might not have valid spans when upgrading 675 if (favoriteType == Favorites.ITEM_TYPE_WIDGET_SEARCH) { 676 values.put(LauncherSettings.Favorites.SPANX, 4); 677 values.put(LauncherSettings.Favorites.SPANY, 1); 678 } else { 679 values.put(LauncherSettings.Favorites.SPANX, 2); 680 values.put(LauncherSettings.Favorites.SPANY, 2); 681 } 682 683 String updateWhere = Favorites._ID + "=" + favoriteId; 684 db.update(TABLE_FAVORITES, values, updateWhere, null); 685 686 if (favoriteType == Favorites.ITEM_TYPE_WIDGET_CLOCK) { 687 appWidgetManager.bindAppWidgetId(appWidgetId, 688 new ComponentName("com.android.alarmclock", 689 "com.android.alarmclock.AnalogAppWidgetProvider")); 690 } else if (favoriteType == Favorites.ITEM_TYPE_WIDGET_PHOTO_FRAME) { 691 appWidgetManager.bindAppWidgetId(appWidgetId, 692 new ComponentName("com.android.camera", 693 "com.android.camera.PhotoAppWidgetProvider")); 694 } else if (favoriteType == Favorites.ITEM_TYPE_WIDGET_SEARCH) { 695 appWidgetManager.bindAppWidgetId(appWidgetId, 696 getSearchWidgetProvider()); 697 } 698 } catch (RuntimeException ex) { 699 Log.e(TAG, "Problem allocating appWidgetId", ex); 700 } 701 } 702 703 db.setTransactionSuccessful(); 704 } catch (SQLException ex) { 705 Log.w(TAG, "Problem while allocating appWidgetIds for existing widgets", ex); 706 } finally { 707 db.endTransaction(); 708 if (c != null) { 709 c.close(); 710 } 711 } 712 } 713 714 /** 715 * Loads the default set of favorite packages from an xml file. 716 * 717 * @param db The database to write the values into 718 * @param filterContainerId The specific container id of items to load 719 */ 720 private int loadFavorites(SQLiteDatabase db, int workspaceResourceId) { 721 Intent intent = new Intent(Intent.ACTION_MAIN, null); 722 intent.addCategory(Intent.CATEGORY_LAUNCHER); 723 ContentValues values = new ContentValues(); 724 725 PackageManager packageManager = mContext.getPackageManager(); 726 int allAppsButtonRank = 727 mContext.getResources().getInteger(R.integer.hotseat_all_apps_index); 728 int i = 0; 729 try { 730 XmlResourceParser parser = mContext.getResources().getXml(workspaceResourceId); 731 AttributeSet attrs = Xml.asAttributeSet(parser); 732 XmlUtils.beginDocument(parser, TAG_FAVORITES); 733 734 final int depth = parser.getDepth(); 735 736 int type; 737 while (((type = parser.next()) != XmlPullParser.END_TAG || 738 parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) { 739 740 if (type != XmlPullParser.START_TAG) { 741 continue; 742 } 743 744 boolean added = false; 745 final String name = parser.getName(); 746 747 TypedArray a = mContext.obtainStyledAttributes(attrs, R.styleable.Favorite); 748 749 long container = LauncherSettings.Favorites.CONTAINER_DESKTOP; 750 if (a.hasValue(R.styleable.Favorite_container)) { 751 container = Long.valueOf(a.getString(R.styleable.Favorite_container)); 752 } 753 754 String screen = a.getString(R.styleable.Favorite_screen); 755 String x = a.getString(R.styleable.Favorite_x); 756 String y = a.getString(R.styleable.Favorite_y); 757 758 // If we are adding to the hotseat, the screen is used as the position in the 759 // hotseat. This screen can't be at position 0 because AllApps is in the 760 // zeroth position. 761 if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT 762 && Integer.valueOf(screen) == allAppsButtonRank) { 763 throw new RuntimeException("Invalid screen position for hotseat item"); 764 } 765 766 values.clear(); 767 values.put(LauncherSettings.Favorites.CONTAINER, container); 768 values.put(LauncherSettings.Favorites.SCREEN, screen); 769 values.put(LauncherSettings.Favorites.CELLX, x); 770 values.put(LauncherSettings.Favorites.CELLY, y); 771 772 if (TAG_FAVORITE.equals(name)) { 773 long id = addAppShortcut(db, values, a, packageManager, intent); 774 added = id >= 0; 775 } else if (TAG_SEARCH.equals(name)) { 776 added = addSearchWidget(db, values); 777 } else if (TAG_CLOCK.equals(name)) { 778 added = addClockWidget(db, values); 779 } else if (TAG_APPWIDGET.equals(name)) { 780 added = addAppWidget(db, values, a, packageManager); 781 } else if (TAG_SHORTCUT.equals(name)) { 782 long id = addUriShortcut(db, values, a); 783 added = id >= 0; 784 } else if (TAG_FOLDER.equals(name)) { 785 String title; 786 int titleResId = a.getResourceId(R.styleable.Favorite_title, -1); 787 if (titleResId != -1) { 788 title = mContext.getResources().getString(titleResId); 789 } else { 790 title = mContext.getResources().getString(R.string.folder_name); 791 } 792 values.put(LauncherSettings.Favorites.TITLE, title); 793 long folderId = addFolder(db, values); 794 added = folderId >= 0; 795 796 ArrayList<Long> folderItems = new ArrayList<Long>(); 797 798 int folderDepth = parser.getDepth(); 799 while ((type = parser.next()) != XmlPullParser.END_TAG || 800 parser.getDepth() > folderDepth) { 801 if (type != XmlPullParser.START_TAG) { 802 continue; 803 } 804 final String folder_item_name = parser.getName(); 805 806 TypedArray ar = mContext.obtainStyledAttributes(attrs, 807 R.styleable.Favorite); 808 values.clear(); 809 values.put(LauncherSettings.Favorites.CONTAINER, folderId); 810 811 if (TAG_FAVORITE.equals(folder_item_name) && folderId >= 0) { 812 long id = 813 addAppShortcut(db, values, ar, packageManager, intent); 814 if (id >= 0) { 815 folderItems.add(id); 816 } 817 } else if (TAG_SHORTCUT.equals(folder_item_name) && folderId >= 0) { 818 long id = addUriShortcut(db, values, ar); 819 if (id >= 0) { 820 folderItems.add(id); 821 } 822 } else { 823 throw new RuntimeException("Folders can " + 824 "contain only shortcuts"); 825 } 826 ar.recycle(); 827 } 828 // We can only have folders with >= 2 items, so we need to remove the 829 // folder and clean up if less than 2 items were included, or some 830 // failed to add, and less than 2 were actually added 831 if (folderItems.size() < 2 && folderId >= 0) { 832 // We just delete the folder and any items that made it 833 deleteId(db, folderId); 834 if (folderItems.size() > 0) { 835 deleteId(db, folderItems.get(0)); 836 } 837 added = false; 838 } 839 } 840 if (added) i++; 841 a.recycle(); 842 } 843 } catch (XmlPullParserException e) { 844 Log.w(TAG, "Got exception parsing favorites.", e); 845 } catch (IOException e) { 846 Log.w(TAG, "Got exception parsing favorites.", e); 847 } catch (RuntimeException e) { 848 Log.w(TAG, "Got exception parsing favorites.", e); 849 } 850 851 return i; 852 } 853 854 private long addAppShortcut(SQLiteDatabase db, ContentValues values, TypedArray a, 855 PackageManager packageManager, Intent intent) { 856 long id = -1; 857 ActivityInfo info; 858 String packageName = a.getString(R.styleable.Favorite_packageName); 859 String className = a.getString(R.styleable.Favorite_className); 860 try { 861 ComponentName cn; 862 try { 863 cn = new ComponentName(packageName, className); 864 info = packageManager.getActivityInfo(cn, 0); 865 } catch (PackageManager.NameNotFoundException nnfe) { 866 String[] packages = packageManager.currentToCanonicalPackageNames( 867 new String[] { packageName }); 868 cn = new ComponentName(packages[0], className); 869 info = packageManager.getActivityInfo(cn, 0); 870 } 871 id = generateNewId(); 872 intent.setComponent(cn); 873 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | 874 Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); 875 values.put(Favorites.INTENT, intent.toUri(0)); 876 values.put(Favorites.TITLE, info.loadLabel(packageManager).toString()); 877 values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_APPLICATION); 878 values.put(Favorites.SPANX, 1); 879 values.put(Favorites.SPANY, 1); 880 values.put(Favorites._ID, generateNewId()); 881 if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values) < 0) { 882 return -1; 883 } 884 } catch (PackageManager.NameNotFoundException e) { 885 Log.w(TAG, "Unable to add favorite: " + packageName + 886 "/" + className, e); 887 } 888 return id; 889 } 890 891 private long addFolder(SQLiteDatabase db, ContentValues values) { 892 values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_FOLDER); 893 values.put(Favorites.SPANX, 1); 894 values.put(Favorites.SPANY, 1); 895 long id = generateNewId(); 896 values.put(Favorites._ID, id); 897 if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values) <= 0) { 898 return -1; 899 } else { 900 return id; 901 } 902 } 903 904 private ComponentName getSearchWidgetProvider() { 905 SearchManager searchManager = 906 (SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE); 907 ComponentName searchComponent = searchManager.getGlobalSearchActivity(); 908 if (searchComponent == null) return null; 909 return getProviderInPackage(searchComponent.getPackageName()); 910 } 911 912 /** 913 * Gets an appwidget provider from the given package. If the package contains more than 914 * one appwidget provider, an arbitrary one is returned. 915 */ 916 private ComponentName getProviderInPackage(String packageName) { 917 AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext); 918 List<AppWidgetProviderInfo> providers = appWidgetManager.getInstalledProviders(); 919 if (providers == null) return null; 920 final int providerCount = providers.size(); 921 for (int i = 0; i < providerCount; i++) { 922 ComponentName provider = providers.get(i).provider; 923 if (provider != null && provider.getPackageName().equals(packageName)) { 924 return provider; 925 } 926 } 927 return null; 928 } 929 930 private boolean addSearchWidget(SQLiteDatabase db, ContentValues values) { 931 ComponentName cn = getSearchWidgetProvider(); 932 return addAppWidget(db, values, cn, 4, 1); 933 } 934 935 private boolean addClockWidget(SQLiteDatabase db, ContentValues values) { 936 ComponentName cn = new ComponentName("com.android.alarmclock", 937 "com.android.alarmclock.AnalogAppWidgetProvider"); 938 return addAppWidget(db, values, cn, 2, 2); 939 } 940 941 private boolean addAppWidget(SQLiteDatabase db, ContentValues values, TypedArray a, 942 PackageManager packageManager) { 943 944 String packageName = a.getString(R.styleable.Favorite_packageName); 945 String className = a.getString(R.styleable.Favorite_className); 946 947 if (packageName == null || className == null) { 948 return false; 949 } 950 951 boolean hasPackage = true; 952 ComponentName cn = new ComponentName(packageName, className); 953 try { 954 packageManager.getReceiverInfo(cn, 0); 955 } catch (Exception e) { 956 String[] packages = packageManager.currentToCanonicalPackageNames( 957 new String[] { packageName }); 958 cn = new ComponentName(packages[0], className); 959 try { 960 packageManager.getReceiverInfo(cn, 0); 961 } catch (Exception e1) { 962 hasPackage = false; 963 } 964 } 965 966 if (hasPackage) { 967 int spanX = a.getInt(R.styleable.Favorite_spanX, 0); 968 int spanY = a.getInt(R.styleable.Favorite_spanY, 0); 969 return addAppWidget(db, values, cn, spanX, spanY); 970 } 971 972 return false; 973 } 974 975 private boolean addAppWidget(SQLiteDatabase db, ContentValues values, ComponentName cn, 976 int spanX, int spanY) { 977 boolean allocatedAppWidgets = false; 978 final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext); 979 980 try { 981 int appWidgetId = mAppWidgetHost.allocateAppWidgetId(); 982 983 values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_APPWIDGET); 984 values.put(Favorites.SPANX, spanX); 985 values.put(Favorites.SPANY, spanY); 986 values.put(Favorites.APPWIDGET_ID, appWidgetId); 987 values.put(Favorites._ID, generateNewId()); 988 dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values); 989 990 allocatedAppWidgets = true; 991 992 appWidgetManager.bindAppWidgetId(appWidgetId, cn); 993 } catch (RuntimeException ex) { 994 Log.e(TAG, "Problem allocating appWidgetId", ex); 995 } 996 997 return allocatedAppWidgets; 998 } 999 1000 private long addUriShortcut(SQLiteDatabase db, ContentValues values, 1001 TypedArray a) { 1002 Resources r = mContext.getResources(); 1003 1004 final int iconResId = a.getResourceId(R.styleable.Favorite_icon, 0); 1005 final int titleResId = a.getResourceId(R.styleable.Favorite_title, 0); 1006 1007 Intent intent; 1008 String uri = null; 1009 try { 1010 uri = a.getString(R.styleable.Favorite_uri); 1011 intent = Intent.parseUri(uri, 0); 1012 } catch (URISyntaxException e) { 1013 Log.w(TAG, "Shortcut has malformed uri: " + uri); 1014 return -1; // Oh well 1015 } 1016 1017 if (iconResId == 0 || titleResId == 0) { 1018 Log.w(TAG, "Shortcut is missing title or icon resource ID"); 1019 return -1; 1020 } 1021 1022 long id = generateNewId(); 1023 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 1024 values.put(Favorites.INTENT, intent.toUri(0)); 1025 values.put(Favorites.TITLE, r.getString(titleResId)); 1026 values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_SHORTCUT); 1027 values.put(Favorites.SPANX, 1); 1028 values.put(Favorites.SPANY, 1); 1029 values.put(Favorites.ICON_TYPE, Favorites.ICON_TYPE_RESOURCE); 1030 values.put(Favorites.ICON_PACKAGE, mContext.getPackageName()); 1031 values.put(Favorites.ICON_RESOURCE, r.getResourceName(iconResId)); 1032 values.put(Favorites._ID, id); 1033 1034 if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values) < 0) { 1035 return -1; 1036 } 1037 return id; 1038 } 1039 } 1040 1041 /** 1042 * Build a query string that will match any row where the column matches 1043 * anything in the values list. 1044 */ 1045 static String buildOrWhereString(String column, int[] values) { 1046 StringBuilder selectWhere = new StringBuilder(); 1047 for (int i = values.length - 1; i >= 0; i--) { 1048 selectWhere.append(column).append("=").append(values[i]); 1049 if (i > 0) { 1050 selectWhere.append(" OR "); 1051 } 1052 } 1053 return selectWhere.toString(); 1054 } 1055 1056 static class SqlArguments { 1057 public final String table; 1058 public final String where; 1059 public final String[] args; 1060 1061 SqlArguments(Uri url, String where, String[] args) { 1062 if (url.getPathSegments().size() == 1) { 1063 this.table = url.getPathSegments().get(0); 1064 this.where = where; 1065 this.args = args; 1066 } else if (url.getPathSegments().size() != 2) { 1067 throw new IllegalArgumentException("Invalid URI: " + url); 1068 } else if (!TextUtils.isEmpty(where)) { 1069 throw new UnsupportedOperationException("WHERE clause not supported: " + url); 1070 } else { 1071 this.table = url.getPathSegments().get(0); 1072 this.where = "_id=" + ContentUris.parseId(url); 1073 this.args = null; 1074 } 1075 } 1076 1077 SqlArguments(Uri url) { 1078 if (url.getPathSegments().size() == 1) { 1079 table = url.getPathSegments().get(0); 1080 where = null; 1081 args = null; 1082 } else { 1083 throw new IllegalArgumentException("Invalid URI: " + url); 1084 } 1085 } 1086 } 1087} 1088