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