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