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