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 96 public boolean onCreate() { 97 mOpenHelper = new DatabaseHelper(getContext()); 98 ((LauncherApplication) getContext()).setLauncherProvider(this); 99 return true; 100 } 101 102 @Override 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 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 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 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 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 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 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 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 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 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 */ 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 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 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 */ 286 private void sendAppWidgetResetNotify() { 287 final ContentResolver resolver = mContext.getContentResolver(); 288 resolver.notifyChange(CONTENT_APPWIDGET_RESET_URI, null); 289 } 290 291 @Override 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 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 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 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 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 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 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 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 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 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 */ 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 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 */ 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 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 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 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 */ 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 1058 private boolean addSearchWidget(SQLiteDatabase db, ContentValues values) { 1059 ComponentName cn = getSearchWidgetProvider(); 1060 return addAppWidget(db, values, cn, 4, 1, null); 1061 } 1062 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 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 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 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 */ 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 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 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