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