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