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.launcher3; 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.ContentProviderOperation; 26 import android.content.ContentProviderResult; 27 import android.content.ContentResolver; 28 import android.content.ContentUris; 29 import android.content.ContentValues; 30 import android.content.Context; 31 import android.content.Intent; 32 import android.content.OperationApplicationException; 33 import android.content.SharedPreferences; 34 import android.content.pm.ActivityInfo; 35 import android.content.pm.PackageManager; 36 import android.content.res.Resources; 37 import android.content.res.TypedArray; 38 import android.content.res.XmlResourceParser; 39 import android.database.Cursor; 40 import android.database.SQLException; 41 import android.database.sqlite.SQLiteDatabase; 42 import android.database.sqlite.SQLiteOpenHelper; 43 import android.database.sqlite.SQLiteQueryBuilder; 44 import android.database.sqlite.SQLiteStatement; 45 import android.graphics.Bitmap; 46 import android.graphics.BitmapFactory; 47 import android.net.Uri; 48 import android.os.Bundle; 49 import android.provider.Settings; 50 import android.text.TextUtils; 51 import android.util.AttributeSet; 52 import android.util.Log; 53 import android.util.SparseArray; 54 import android.util.Xml; 55 56 import com.android.launcher3.LauncherSettings.Favorites; 57 import com.android.launcher3.config.ProviderConfig; 58 59 import org.xmlpull.v1.XmlPullParser; 60 import org.xmlpull.v1.XmlPullParserException; 61 62 import java.io.File; 63 import java.io.IOException; 64 import java.net.URISyntaxException; 65 import java.util.ArrayList; 66 import java.util.HashSet; 67 import java.util.List; 68 69 public class LauncherProvider extends ContentProvider { 70 private static final String TAG = "Launcher.LauncherProvider"; 71 private static final boolean LOGD = false; 72 73 private static final String DATABASE_NAME = "launcher.db"; 74 75 private static final int DATABASE_VERSION = 17; 76 77 static final String OLD_AUTHORITY = "com.android.launcher2.settings"; 78 static final String AUTHORITY = ProviderConfig.AUTHORITY; 79 80 // Should we attempt to load anything from the com.android.launcher2 provider? 81 static final boolean IMPORT_LAUNCHER2_DATABASE = false; 82 83 static final String TABLE_FAVORITES = "favorites"; 84 static final String TABLE_WORKSPACE_SCREENS = "workspaceScreens"; 85 static final String PARAMETER_NOTIFY = "notify"; 86 static final String UPGRADED_FROM_OLD_DATABASE = 87 "UPGRADED_FROM_OLD_DATABASE"; 88 static final String EMPTY_DATABASE_CREATED = 89 "EMPTY_DATABASE_CREATED"; 90 static final String DEFAULT_WORKSPACE_RESOURCE_ID = 91 "DEFAULT_WORKSPACE_RESOURCE_ID"; 92 93 private static final String ACTION_APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE = 94 "com.android.launcher.action.APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE"; 95 96 /** 97 * {@link Uri} triggered at any registered {@link android.database.ContentObserver} when 98 * {@link AppWidgetHost#deleteHost()} is called during database creation. 99 * Use this to recall {@link AppWidgetHost#startListening()} if needed. 100 */ 101 static final Uri CONTENT_APPWIDGET_RESET_URI = 102 Uri.parse("content://" + AUTHORITY + "/appWidgetReset"); 103 104 private DatabaseHelper mOpenHelper; 105 private static boolean sJustLoadedFromOldDb; 106 107 @Override 108 public boolean onCreate() { 109 final Context context = getContext(); 110 mOpenHelper = new DatabaseHelper(context); 111 LauncherAppState.setLauncherProvider(this); 112 return true; 113 } 114 115 public boolean wasNewDbCreated() { 116 return mOpenHelper.wasNewDbCreated(); 117 } 118 119 @Override 120 public String getType(Uri uri) { 121 SqlArguments args = new SqlArguments(uri, null, null); 122 if (TextUtils.isEmpty(args.where)) { 123 return "vnd.android.cursor.dir/" + args.table; 124 } else { 125 return "vnd.android.cursor.item/" + args.table; 126 } 127 } 128 129 @Override 130 public Cursor query(Uri uri, String[] projection, String selection, 131 String[] selectionArgs, String sortOrder) { 132 133 SqlArguments args = new SqlArguments(uri, selection, selectionArgs); 134 SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); 135 qb.setTables(args.table); 136 137 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 138 Cursor result = qb.query(db, projection, args.where, args.args, null, null, sortOrder); 139 result.setNotificationUri(getContext().getContentResolver(), uri); 140 141 return result; 142 } 143 144 private static long dbInsertAndCheck(DatabaseHelper helper, 145 SQLiteDatabase db, String table, String nullColumnHack, ContentValues values) { 146 if (values == null) { 147 throw new RuntimeException("Error: attempting to insert null values"); 148 } 149 if (!values.containsKey(LauncherSettings.BaseLauncherColumns._ID)) { 150 throw new RuntimeException("Error: attempting to add item without specifying an id"); 151 } 152 helper.checkId(table, values); 153 return db.insert(table, nullColumnHack, values); 154 } 155 156 private static void deleteId(SQLiteDatabase db, long id) { 157 Uri uri = LauncherSettings.Favorites.getContentUri(id, false); 158 SqlArguments args = new SqlArguments(uri, null, null); 159 db.delete(args.table, args.where, args.args); 160 } 161 162 @Override 163 public Uri insert(Uri uri, ContentValues initialValues) { 164 SqlArguments args = new SqlArguments(uri); 165 166 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 167 addModifiedTime(initialValues); 168 final long rowId = dbInsertAndCheck(mOpenHelper, db, args.table, null, initialValues); 169 if (rowId <= 0) return null; 170 171 uri = ContentUris.withAppendedId(uri, rowId); 172 sendNotify(uri); 173 174 return uri; 175 } 176 177 @Override 178 public int bulkInsert(Uri uri, ContentValues[] values) { 179 SqlArguments args = new SqlArguments(uri); 180 181 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 182 db.beginTransaction(); 183 try { 184 int numValues = values.length; 185 for (int i = 0; i < numValues; i++) { 186 addModifiedTime(values[i]); 187 if (dbInsertAndCheck(mOpenHelper, db, args.table, null, values[i]) < 0) { 188 return 0; 189 } 190 } 191 db.setTransactionSuccessful(); 192 } finally { 193 db.endTransaction(); 194 } 195 196 sendNotify(uri); 197 return values.length; 198 } 199 200 @Override 201 public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations) 202 throws OperationApplicationException { 203 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 204 db.beginTransaction(); 205 try { 206 ContentProviderResult[] result = super.applyBatch(operations); 207 db.setTransactionSuccessful(); 208 return result; 209 } finally { 210 db.endTransaction(); 211 } 212 } 213 214 @Override 215 public int delete(Uri uri, String selection, String[] selectionArgs) { 216 SqlArguments args = new SqlArguments(uri, selection, selectionArgs); 217 218 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 219 int count = db.delete(args.table, args.where, args.args); 220 if (count > 0) sendNotify(uri); 221 222 return count; 223 } 224 225 @Override 226 public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { 227 SqlArguments args = new SqlArguments(uri, selection, selectionArgs); 228 229 addModifiedTime(values); 230 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 231 int count = db.update(args.table, values, args.where, args.args); 232 if (count > 0) sendNotify(uri); 233 234 return count; 235 } 236 237 private void sendNotify(Uri uri) { 238 String notify = uri.getQueryParameter(PARAMETER_NOTIFY); 239 if (notify == null || "true".equals(notify)) { 240 getContext().getContentResolver().notifyChange(uri, null); 241 } 242 243 // always notify the backup agent 244 LauncherBackupAgentHelper.dataChanged(getContext()); 245 } 246 247 private void addModifiedTime(ContentValues values) { 248 values.put(LauncherSettings.ChangeLogColumns.MODIFIED, System.currentTimeMillis()); 249 } 250 251 public long generateNewItemId() { 252 return mOpenHelper.generateNewItemId(); 253 } 254 255 public void updateMaxItemId(long id) { 256 mOpenHelper.updateMaxItemId(id); 257 } 258 259 public long generateNewScreenId() { 260 return mOpenHelper.generateNewScreenId(); 261 } 262 263 // This is only required one time while loading the workspace during the 264 // upgrade path, and should never be called from anywhere else. 265 public void updateMaxScreenId(long maxScreenId) { 266 mOpenHelper.updateMaxScreenId(maxScreenId); 267 } 268 269 /** 270 * @param Should we load the old db for upgrade? first run only. 271 */ 272 synchronized public boolean justLoadedOldDb() { 273 String spKey = LauncherAppState.getSharedPreferencesKey(); 274 SharedPreferences sp = getContext().getSharedPreferences(spKey, Context.MODE_PRIVATE); 275 276 boolean loadedOldDb = false || sJustLoadedFromOldDb; 277 278 sJustLoadedFromOldDb = false; 279 if (sp.getBoolean(UPGRADED_FROM_OLD_DATABASE, false)) { 280 281 SharedPreferences.Editor editor = sp.edit(); 282 editor.remove(UPGRADED_FROM_OLD_DATABASE); 283 editor.commit(); 284 loadedOldDb = true; 285 } 286 return loadedOldDb; 287 } 288 289 /** 290 * @param workspaceResId that can be 0 to use default or non-zero for specific resource 291 */ 292 synchronized public void loadDefaultFavoritesIfNecessary(int origWorkspaceResId) { 293 String spKey = LauncherAppState.getSharedPreferencesKey(); 294 SharedPreferences sp = getContext().getSharedPreferences(spKey, Context.MODE_PRIVATE); 295 296 if (sp.getBoolean(EMPTY_DATABASE_CREATED, false)) { 297 Log.d(TAG, "loading default workspace"); 298 int workspaceResId = origWorkspaceResId; 299 300 // Use default workspace resource if none provided 301 if (workspaceResId == 0) { 302 workspaceResId = 303 sp.getInt(DEFAULT_WORKSPACE_RESOURCE_ID, getDefaultWorkspaceResourceId()); 304 } 305 306 // Populate favorites table with initial favorites 307 SharedPreferences.Editor editor = sp.edit(); 308 editor.remove(EMPTY_DATABASE_CREATED); 309 if (origWorkspaceResId != 0) { 310 editor.putInt(DEFAULT_WORKSPACE_RESOURCE_ID, origWorkspaceResId); 311 } 312 313 mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(), workspaceResId); 314 mOpenHelper.setFlagJustLoadedOldDb(); 315 editor.commit(); 316 } 317 } 318 319 public void migrateLauncher2Shortcuts() { 320 mOpenHelper.migrateLauncher2Shortcuts(mOpenHelper.getWritableDatabase(), 321 LauncherSettings.Favorites.OLD_CONTENT_URI); 322 } 323 324 private static int getDefaultWorkspaceResourceId() { 325 if (LauncherAppState.isDisableAllApps()) { 326 return R.xml.default_workspace_no_all_apps; 327 } else { 328 return R.xml.default_workspace; 329 } 330 } 331 332 private static interface ContentValuesCallback { 333 public void onRow(ContentValues values); 334 } 335 336 private static boolean shouldImportLauncher2Database(Context context) { 337 boolean isTablet = context.getResources().getBoolean(R.bool.is_tablet); 338 339 // We don't import the old databse for tablets, as the grid size has changed. 340 return !isTablet && IMPORT_LAUNCHER2_DATABASE; 341 } 342 343 public void deleteDatabase() { 344 // Are you sure? (y/n) 345 final SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 346 final File dbFile = new File(db.getPath()); 347 mOpenHelper.close(); 348 if (dbFile.exists()) { 349 SQLiteDatabase.deleteDatabase(dbFile); 350 } 351 mOpenHelper = new DatabaseHelper(getContext()); 352 } 353 354 private static class DatabaseHelper extends SQLiteOpenHelper { 355 private static final String TAG_FAVORITES = "favorites"; 356 private static final String TAG_FAVORITE = "favorite"; 357 private static final String TAG_CLOCK = "clock"; 358 private static final String TAG_SEARCH = "search"; 359 private static final String TAG_APPWIDGET = "appwidget"; 360 private static final String TAG_SHORTCUT = "shortcut"; 361 private static final String TAG_FOLDER = "folder"; 362 private static final String TAG_EXTRA = "extra"; 363 private static final String TAG_INCLUDE = "include"; 364 365 private final Context mContext; 366 private final AppWidgetHost mAppWidgetHost; 367 private long mMaxItemId = -1; 368 private long mMaxScreenId = -1; 369 370 private boolean mNewDbCreated = false; 371 372 DatabaseHelper(Context context) { 373 super(context, DATABASE_NAME, null, DATABASE_VERSION); 374 mContext = context; 375 mAppWidgetHost = new AppWidgetHost(context, Launcher.APPWIDGET_HOST_ID); 376 377 // In the case where neither onCreate nor onUpgrade gets called, we read the maxId from 378 // the DB here 379 if (mMaxItemId == -1) { 380 mMaxItemId = initializeMaxItemId(getWritableDatabase()); 381 } 382 if (mMaxScreenId == -1) { 383 mMaxScreenId = initializeMaxScreenId(getWritableDatabase()); 384 } 385 } 386 387 public boolean wasNewDbCreated() { 388 return mNewDbCreated; 389 } 390 391 /** 392 * Send notification that we've deleted the {@link AppWidgetHost}, 393 * probably as part of the initial database creation. The receiver may 394 * want to re-call {@link AppWidgetHost#startListening()} to ensure 395 * callbacks are correctly set. 396 */ 397 private void sendAppWidgetResetNotify() { 398 final ContentResolver resolver = mContext.getContentResolver(); 399 resolver.notifyChange(CONTENT_APPWIDGET_RESET_URI, null); 400 } 401 402 @Override 403 public void onCreate(SQLiteDatabase db) { 404 if (LOGD) Log.d(TAG, "creating new launcher database"); 405 406 mMaxItemId = 1; 407 mMaxScreenId = 0; 408 mNewDbCreated = true; 409 410 db.execSQL("CREATE TABLE favorites (" + 411 "_id INTEGER PRIMARY KEY," + 412 "title TEXT," + 413 "intent TEXT," + 414 "container INTEGER," + 415 "screen INTEGER," + 416 "cellX INTEGER," + 417 "cellY INTEGER," + 418 "spanX INTEGER," + 419 "spanY INTEGER," + 420 "itemType INTEGER," + 421 "appWidgetId INTEGER NOT NULL DEFAULT -1," + 422 "isShortcut INTEGER," + 423 "iconType INTEGER," + 424 "iconPackage TEXT," + 425 "iconResource TEXT," + 426 "icon BLOB," + 427 "uri TEXT," + 428 "displayMode INTEGER," + 429 "appWidgetProvider TEXT," + 430 "modified INTEGER NOT NULL DEFAULT 0," + 431 "restored INTEGER NOT NULL DEFAULT 0" + 432 ");"); 433 addWorkspacesTable(db); 434 435 // Database was just created, so wipe any previous widgets 436 if (mAppWidgetHost != null) { 437 mAppWidgetHost.deleteHost(); 438 sendAppWidgetResetNotify(); 439 } 440 441 if (shouldImportLauncher2Database(mContext)) { 442 // Try converting the old database 443 ContentValuesCallback permuteScreensCb = new ContentValuesCallback() { 444 public void onRow(ContentValues values) { 445 int container = values.getAsInteger(LauncherSettings.Favorites.CONTAINER); 446 if (container == Favorites.CONTAINER_DESKTOP) { 447 int screen = values.getAsInteger(LauncherSettings.Favorites.SCREEN); 448 screen = (int) upgradeLauncherDb_permuteScreens(screen); 449 values.put(LauncherSettings.Favorites.SCREEN, screen); 450 } 451 } 452 }; 453 Uri uri = Uri.parse("content://" + Settings.AUTHORITY + 454 "/old_favorites?notify=true"); 455 if (!convertDatabase(db, uri, permuteScreensCb, true)) { 456 // Try and upgrade from the Launcher2 db 457 uri = LauncherSettings.Favorites.OLD_CONTENT_URI; 458 if (!convertDatabase(db, uri, permuteScreensCb, false)) { 459 // If we fail, then set a flag to load the default workspace 460 setFlagEmptyDbCreated(); 461 return; 462 } 463 } 464 // Right now, in non-default workspace cases, we want to run the final 465 // upgrade code (ie. to fix workspace screen indices -> ids, etc.), so 466 // set that flag too. 467 setFlagJustLoadedOldDb(); 468 } else { 469 // Fresh and clean launcher DB. 470 mMaxItemId = initializeMaxItemId(db); 471 setFlagEmptyDbCreated(); 472 } 473 } 474 475 private void addWorkspacesTable(SQLiteDatabase db) { 476 db.execSQL("CREATE TABLE " + TABLE_WORKSPACE_SCREENS + " (" + 477 LauncherSettings.WorkspaceScreens._ID + " INTEGER," + 478 LauncherSettings.WorkspaceScreens.SCREEN_RANK + " INTEGER," + 479 LauncherSettings.ChangeLogColumns.MODIFIED + " INTEGER NOT NULL DEFAULT 0" + 480 ");"); 481 } 482 483 private void setFlagJustLoadedOldDb() { 484 String spKey = LauncherAppState.getSharedPreferencesKey(); 485 SharedPreferences sp = mContext.getSharedPreferences(spKey, Context.MODE_PRIVATE); 486 SharedPreferences.Editor editor = sp.edit(); 487 editor.putBoolean(UPGRADED_FROM_OLD_DATABASE, true); 488 editor.putBoolean(EMPTY_DATABASE_CREATED, false); 489 editor.commit(); 490 } 491 492 private void setFlagEmptyDbCreated() { 493 String spKey = LauncherAppState.getSharedPreferencesKey(); 494 SharedPreferences sp = mContext.getSharedPreferences(spKey, Context.MODE_PRIVATE); 495 SharedPreferences.Editor editor = sp.edit(); 496 editor.putBoolean(EMPTY_DATABASE_CREATED, true); 497 editor.putBoolean(UPGRADED_FROM_OLD_DATABASE, false); 498 editor.commit(); 499 } 500 501 // We rearrange the screens from the old launcher 502 // 12345 -> 34512 503 private long upgradeLauncherDb_permuteScreens(long screen) { 504 if (screen >= 2) { 505 return screen - 2; 506 } else { 507 return screen + 3; 508 } 509 } 510 511 private boolean convertDatabase(SQLiteDatabase db, Uri uri, 512 ContentValuesCallback cb, boolean deleteRows) { 513 if (LOGD) Log.d(TAG, "converting database from an older format, but not onUpgrade"); 514 boolean converted = false; 515 516 final ContentResolver resolver = mContext.getContentResolver(); 517 Cursor cursor = null; 518 519 try { 520 cursor = resolver.query(uri, null, null, null, null); 521 } catch (Exception e) { 522 // Ignore 523 } 524 525 // We already have a favorites database in the old provider 526 if (cursor != null) { 527 try { 528 if (cursor.getCount() > 0) { 529 converted = copyFromCursor(db, cursor, cb) > 0; 530 if (converted && deleteRows) { 531 resolver.delete(uri, null, null); 532 } 533 } 534 } finally { 535 cursor.close(); 536 } 537 } 538 539 if (converted) { 540 // Convert widgets from this import into widgets 541 if (LOGD) Log.d(TAG, "converted and now triggering widget upgrade"); 542 convertWidgets(db); 543 544 // Update max item id 545 mMaxItemId = initializeMaxItemId(db); 546 if (LOGD) Log.d(TAG, "mMaxItemId: " + mMaxItemId); 547 } 548 549 return converted; 550 } 551 552 private int copyFromCursor(SQLiteDatabase db, Cursor c, ContentValuesCallback cb) { 553 final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID); 554 final int intentIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.INTENT); 555 final int titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE); 556 final int iconTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_TYPE); 557 final int iconIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON); 558 final int iconPackageIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_PACKAGE); 559 final int iconResourceIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_RESOURCE); 560 final int containerIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER); 561 final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE); 562 final int screenIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN); 563 final int cellXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX); 564 final int cellYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY); 565 final int uriIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.URI); 566 final int displayModeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.DISPLAY_MODE); 567 568 ContentValues[] rows = new ContentValues[c.getCount()]; 569 int i = 0; 570 while (c.moveToNext()) { 571 ContentValues values = new ContentValues(c.getColumnCount()); 572 values.put(LauncherSettings.Favorites._ID, c.getLong(idIndex)); 573 values.put(LauncherSettings.Favorites.INTENT, c.getString(intentIndex)); 574 values.put(LauncherSettings.Favorites.TITLE, c.getString(titleIndex)); 575 values.put(LauncherSettings.Favorites.ICON_TYPE, c.getInt(iconTypeIndex)); 576 values.put(LauncherSettings.Favorites.ICON, c.getBlob(iconIndex)); 577 values.put(LauncherSettings.Favorites.ICON_PACKAGE, c.getString(iconPackageIndex)); 578 values.put(LauncherSettings.Favorites.ICON_RESOURCE, c.getString(iconResourceIndex)); 579 values.put(LauncherSettings.Favorites.CONTAINER, c.getInt(containerIndex)); 580 values.put(LauncherSettings.Favorites.ITEM_TYPE, c.getInt(itemTypeIndex)); 581 values.put(LauncherSettings.Favorites.APPWIDGET_ID, -1); 582 values.put(LauncherSettings.Favorites.SCREEN, c.getInt(screenIndex)); 583 values.put(LauncherSettings.Favorites.CELLX, c.getInt(cellXIndex)); 584 values.put(LauncherSettings.Favorites.CELLY, c.getInt(cellYIndex)); 585 values.put(LauncherSettings.Favorites.URI, c.getString(uriIndex)); 586 values.put(LauncherSettings.Favorites.DISPLAY_MODE, c.getInt(displayModeIndex)); 587 if (cb != null) { 588 cb.onRow(values); 589 } 590 rows[i++] = values; 591 } 592 593 int total = 0; 594 if (i > 0) { 595 db.beginTransaction(); 596 try { 597 int numValues = rows.length; 598 for (i = 0; i < numValues; i++) { 599 if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, rows[i]) < 0) { 600 return 0; 601 } else { 602 total++; 603 } 604 } 605 db.setTransactionSuccessful(); 606 } finally { 607 db.endTransaction(); 608 } 609 } 610 611 return total; 612 } 613 614 @Override 615 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 616 if (LOGD) Log.d(TAG, "onUpgrade triggered: " + oldVersion); 617 618 int version = oldVersion; 619 if (version < 3) { 620 // upgrade 1,2 -> 3 added appWidgetId column 621 db.beginTransaction(); 622 try { 623 // Insert new column for holding appWidgetIds 624 db.execSQL("ALTER TABLE favorites " + 625 "ADD COLUMN appWidgetId INTEGER NOT NULL DEFAULT -1;"); 626 db.setTransactionSuccessful(); 627 version = 3; 628 } catch (SQLException ex) { 629 // Old version remains, which means we wipe old data 630 Log.e(TAG, ex.getMessage(), ex); 631 } finally { 632 db.endTransaction(); 633 } 634 635 // Convert existing widgets only if table upgrade was successful 636 if (version == 3) { 637 convertWidgets(db); 638 } 639 } 640 641 if (version < 4) { 642 version = 4; 643 } 644 645 // Where's version 5? 646 // - Donut and sholes on 2.0 shipped with version 4 of launcher1. 647 // - Passion shipped on 2.1 with version 6 of launcher3 648 // - Sholes shipped on 2.1r1 (aka Mr. 3) with version 5 of launcher 1 649 // but version 5 on there was the updateContactsShortcuts change 650 // which was version 6 in launcher 2 (first shipped on passion 2.1r1). 651 // The updateContactsShortcuts change is idempotent, so running it twice 652 // is okay so we'll do that when upgrading the devices that shipped with it. 653 if (version < 6) { 654 // We went from 3 to 5 screens. Move everything 1 to the right 655 db.beginTransaction(); 656 try { 657 db.execSQL("UPDATE favorites SET screen=(screen + 1);"); 658 db.setTransactionSuccessful(); 659 } catch (SQLException ex) { 660 // Old version remains, which means we wipe old data 661 Log.e(TAG, ex.getMessage(), ex); 662 } finally { 663 db.endTransaction(); 664 } 665 666 // We added the fast track. 667 if (updateContactsShortcuts(db)) { 668 version = 6; 669 } 670 } 671 672 if (version < 7) { 673 // Version 7 gets rid of the special search widget. 674 convertWidgets(db); 675 version = 7; 676 } 677 678 if (version < 8) { 679 // Version 8 (froyo) has the icons all normalized. This should 680 // already be the case in practice, but we now rely on it and don't 681 // resample the images each time. 682 normalizeIcons(db); 683 version = 8; 684 } 685 686 if (version < 9) { 687 // The max id is not yet set at this point (onUpgrade is triggered in the ctor 688 // before it gets a change to get set, so we need to read it here when we use it) 689 if (mMaxItemId == -1) { 690 mMaxItemId = initializeMaxItemId(db); 691 } 692 693 // Add default hotseat icons 694 loadFavorites(db, R.xml.update_workspace); 695 version = 9; 696 } 697 698 // We bumped the version three time during JB, once to update the launch flags, once to 699 // update the override for the default launch animation and once to set the mimetype 700 // to improve startup performance 701 if (version < 12) { 702 // Contact shortcuts need a different set of flags to be launched now 703 // The updateContactsShortcuts change is idempotent, so we can keep using it like 704 // back in the Donut days 705 updateContactsShortcuts(db); 706 version = 12; 707 } 708 709 if (version < 13) { 710 // With the new shrink-wrapped and re-orderable workspaces, it makes sense 711 // to persist workspace screens and their relative order. 712 mMaxScreenId = 0; 713 714 // This will never happen in the wild, but when we switch to using workspace 715 // screen ids, redo the import from old launcher. 716 sJustLoadedFromOldDb = true; 717 718 addWorkspacesTable(db); 719 version = 13; 720 } 721 722 if (version < 14) { 723 db.beginTransaction(); 724 try { 725 // Insert new column for holding widget provider name 726 db.execSQL("ALTER TABLE favorites " + 727 "ADD COLUMN appWidgetProvider TEXT;"); 728 db.setTransactionSuccessful(); 729 version = 14; 730 } catch (SQLException ex) { 731 // Old version remains, which means we wipe old data 732 Log.e(TAG, ex.getMessage(), ex); 733 } finally { 734 db.endTransaction(); 735 } 736 } 737 738 if (version < 15) { 739 db.beginTransaction(); 740 try { 741 // Insert new column for holding update timestamp 742 db.execSQL("ALTER TABLE favorites " + 743 "ADD COLUMN modified INTEGER NOT NULL DEFAULT 0;"); 744 db.execSQL("ALTER TABLE workspaceScreens " + 745 "ADD COLUMN modified INTEGER NOT NULL DEFAULT 0;"); 746 db.setTransactionSuccessful(); 747 version = 15; 748 } catch (SQLException ex) { 749 // Old version remains, which means we wipe old data 750 Log.e(TAG, ex.getMessage(), ex); 751 } finally { 752 db.endTransaction(); 753 } 754 } 755 756 757 if (version < 16) { 758 db.beginTransaction(); 759 try { 760 // Insert new column for holding restore status 761 db.execSQL("ALTER TABLE favorites " + 762 "ADD COLUMN restored INTEGER NOT NULL DEFAULT 0;"); 763 db.setTransactionSuccessful(); 764 version = 16; 765 } catch (SQLException ex) { 766 // Old version remains, which means we wipe old data 767 Log.e(TAG, ex.getMessage(), ex); 768 } finally { 769 db.endTransaction(); 770 } 771 } 772 773 if (version < 17) { 774 // We use the db version upgrade here to identify users who may not have seen 775 // clings yet (because they weren't available), but for whom the clings are now 776 // available (tablet users). Because one of the possible cling flows (migration) 777 // is very destructive (wipes out workspaces), we want to prevent this from showing 778 // until clear data. We do so by marking that the clings have been shown. 779 LauncherClings.synchonouslyMarkFirstRunClingDismissed(mContext); 780 version = 17; 781 } 782 783 if (version != DATABASE_VERSION) { 784 Log.w(TAG, "Destroying all old data."); 785 db.execSQL("DROP TABLE IF EXISTS " + TABLE_FAVORITES); 786 db.execSQL("DROP TABLE IF EXISTS " + TABLE_WORKSPACE_SCREENS); 787 788 onCreate(db); 789 } 790 } 791 792 private boolean updateContactsShortcuts(SQLiteDatabase db) { 793 final String selectWhere = buildOrWhereString(Favorites.ITEM_TYPE, 794 new int[] { Favorites.ITEM_TYPE_SHORTCUT }); 795 796 Cursor c = null; 797 final String actionQuickContact = "com.android.contacts.action.QUICK_CONTACT"; 798 db.beginTransaction(); 799 try { 800 // Select and iterate through each matching widget 801 c = db.query(TABLE_FAVORITES, 802 new String[] { Favorites._ID, Favorites.INTENT }, 803 selectWhere, null, null, null, null); 804 if (c == null) return false; 805 806 if (LOGD) Log.d(TAG, "found upgrade cursor count=" + c.getCount()); 807 808 final int idIndex = c.getColumnIndex(Favorites._ID); 809 final int intentIndex = c.getColumnIndex(Favorites.INTENT); 810 811 while (c.moveToNext()) { 812 long favoriteId = c.getLong(idIndex); 813 final String intentUri = c.getString(intentIndex); 814 if (intentUri != null) { 815 try { 816 final Intent intent = Intent.parseUri(intentUri, 0); 817 android.util.Log.d("Home", intent.toString()); 818 final Uri uri = intent.getData(); 819 if (uri != null) { 820 final String data = uri.toString(); 821 if ((Intent.ACTION_VIEW.equals(intent.getAction()) || 822 actionQuickContact.equals(intent.getAction())) && 823 (data.startsWith("content://contacts/people/") || 824 data.startsWith("content://com.android.contacts/" + 825 "contacts/lookup/"))) { 826 827 final Intent newIntent = new Intent(actionQuickContact); 828 // When starting from the launcher, start in a new, cleared task 829 // CLEAR_WHEN_TASK_RESET cannot reset the root of a task, so we 830 // clear the whole thing preemptively here since 831 // QuickContactActivity will finish itself when launching other 832 // detail activities. 833 newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | 834 Intent.FLAG_ACTIVITY_CLEAR_TASK); 835 newIntent.putExtra( 836 Launcher.INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION, true); 837 newIntent.setData(uri); 838 // Determine the type and also put that in the shortcut 839 // (that can speed up launch a bit) 840 newIntent.setDataAndType(uri, newIntent.resolveType(mContext)); 841 842 final ContentValues values = new ContentValues(); 843 values.put(LauncherSettings.Favorites.INTENT, 844 newIntent.toUri(0)); 845 846 String updateWhere = Favorites._ID + "=" + favoriteId; 847 db.update(TABLE_FAVORITES, values, updateWhere, null); 848 } 849 } 850 } catch (RuntimeException ex) { 851 Log.e(TAG, "Problem upgrading shortcut", ex); 852 } catch (URISyntaxException e) { 853 Log.e(TAG, "Problem upgrading shortcut", e); 854 } 855 } 856 } 857 858 db.setTransactionSuccessful(); 859 } catch (SQLException ex) { 860 Log.w(TAG, "Problem while upgrading contacts", ex); 861 return false; 862 } finally { 863 db.endTransaction(); 864 if (c != null) { 865 c.close(); 866 } 867 } 868 869 return true; 870 } 871 872 private void normalizeIcons(SQLiteDatabase db) { 873 Log.d(TAG, "normalizing icons"); 874 875 db.beginTransaction(); 876 Cursor c = null; 877 SQLiteStatement update = null; 878 try { 879 boolean logged = false; 880 update = db.compileStatement("UPDATE favorites " 881 + "SET icon=? WHERE _id=?"); 882 883 c = db.rawQuery("SELECT _id, icon FROM favorites WHERE iconType=" + 884 Favorites.ICON_TYPE_BITMAP, null); 885 886 final int idIndex = c.getColumnIndexOrThrow(Favorites._ID); 887 final int iconIndex = c.getColumnIndexOrThrow(Favorites.ICON); 888 889 while (c.moveToNext()) { 890 long id = c.getLong(idIndex); 891 byte[] data = c.getBlob(iconIndex); 892 try { 893 Bitmap bitmap = Utilities.resampleIconBitmap( 894 BitmapFactory.decodeByteArray(data, 0, data.length), 895 mContext); 896 if (bitmap != null) { 897 update.bindLong(1, id); 898 data = ItemInfo.flattenBitmap(bitmap); 899 if (data != null) { 900 update.bindBlob(2, data); 901 update.execute(); 902 } 903 bitmap.recycle(); 904 } 905 } catch (Exception e) { 906 if (!logged) { 907 Log.e(TAG, "Failed normalizing icon " + id, e); 908 } else { 909 Log.e(TAG, "Also failed normalizing icon " + id); 910 } 911 logged = true; 912 } 913 } 914 db.setTransactionSuccessful(); 915 } catch (SQLException ex) { 916 Log.w(TAG, "Problem while allocating appWidgetIds for existing widgets", ex); 917 } finally { 918 db.endTransaction(); 919 if (update != null) { 920 update.close(); 921 } 922 if (c != null) { 923 c.close(); 924 } 925 } 926 } 927 928 // Generates a new ID to use for an object in your database. This method should be only 929 // called from the main UI thread. As an exception, we do call it when we call the 930 // constructor from the worker thread; however, this doesn't extend until after the 931 // constructor is called, and we only pass a reference to LauncherProvider to LauncherApp 932 // after that point 933 public long generateNewItemId() { 934 if (mMaxItemId < 0) { 935 throw new RuntimeException("Error: max item id was not initialized"); 936 } 937 mMaxItemId += 1; 938 return mMaxItemId; 939 } 940 941 public void updateMaxItemId(long id) { 942 mMaxItemId = id + 1; 943 } 944 945 public void checkId(String table, ContentValues values) { 946 long id = values.getAsLong(LauncherSettings.BaseLauncherColumns._ID); 947 if (table == LauncherProvider.TABLE_WORKSPACE_SCREENS) { 948 mMaxScreenId = Math.max(id, mMaxScreenId); 949 } else { 950 mMaxItemId = Math.max(id, mMaxItemId); 951 } 952 } 953 954 private long initializeMaxItemId(SQLiteDatabase db) { 955 Cursor c = db.rawQuery("SELECT MAX(_id) FROM favorites", null); 956 957 // get the result 958 final int maxIdIndex = 0; 959 long id = -1; 960 if (c != null && c.moveToNext()) { 961 id = c.getLong(maxIdIndex); 962 } 963 if (c != null) { 964 c.close(); 965 } 966 967 if (id == -1) { 968 throw new RuntimeException("Error: could not query max item id"); 969 } 970 971 return id; 972 } 973 974 // Generates a new ID to use for an workspace screen in your database. This method 975 // should be only called from the main UI thread. As an exception, we do call it when we 976 // call the constructor from the worker thread; however, this doesn't extend until after the 977 // constructor is called, and we only pass a reference to LauncherProvider to LauncherApp 978 // after that point 979 public long generateNewScreenId() { 980 if (mMaxScreenId < 0) { 981 throw new RuntimeException("Error: max screen id was not initialized"); 982 } 983 mMaxScreenId += 1; 984 // Log to disk 985 Launcher.addDumpLog(TAG, "11683562 - generateNewScreenId(): " + mMaxScreenId, true); 986 return mMaxScreenId; 987 } 988 989 public void updateMaxScreenId(long maxScreenId) { 990 // Log to disk 991 Launcher.addDumpLog(TAG, "11683562 - updateMaxScreenId(): " + maxScreenId, true); 992 mMaxScreenId = maxScreenId; 993 } 994 995 private long initializeMaxScreenId(SQLiteDatabase db) { 996 Cursor c = db.rawQuery("SELECT MAX(" + LauncherSettings.WorkspaceScreens._ID + ") FROM " + TABLE_WORKSPACE_SCREENS, null); 997 998 // get the result 999 final int maxIdIndex = 0; 1000 long id = -1; 1001 if (c != null && c.moveToNext()) { 1002 id = c.getLong(maxIdIndex); 1003 } 1004 if (c != null) { 1005 c.close(); 1006 } 1007 1008 if (id == -1) { 1009 throw new RuntimeException("Error: could not query max screen id"); 1010 } 1011 1012 // Log to disk 1013 Launcher.addDumpLog(TAG, "11683562 - initializeMaxScreenId(): " + id, true); 1014 return id; 1015 } 1016 1017 /** 1018 * Upgrade existing clock and photo frame widgets into their new widget 1019 * equivalents. 1020 */ 1021 private void convertWidgets(SQLiteDatabase db) { 1022 final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext); 1023 final int[] bindSources = new int[] { 1024 Favorites.ITEM_TYPE_WIDGET_CLOCK, 1025 Favorites.ITEM_TYPE_WIDGET_PHOTO_FRAME, 1026 Favorites.ITEM_TYPE_WIDGET_SEARCH, 1027 }; 1028 1029 final String selectWhere = buildOrWhereString(Favorites.ITEM_TYPE, bindSources); 1030 1031 Cursor c = null; 1032 1033 db.beginTransaction(); 1034 try { 1035 // Select and iterate through each matching widget 1036 c = db.query(TABLE_FAVORITES, new String[] { Favorites._ID, Favorites.ITEM_TYPE }, 1037 selectWhere, null, null, null, null); 1038 1039 if (LOGD) Log.d(TAG, "found upgrade cursor count=" + c.getCount()); 1040 1041 final ContentValues values = new ContentValues(); 1042 while (c != null && c.moveToNext()) { 1043 long favoriteId = c.getLong(0); 1044 int favoriteType = c.getInt(1); 1045 1046 // Allocate and update database with new appWidgetId 1047 try { 1048 int appWidgetId = mAppWidgetHost.allocateAppWidgetId(); 1049 1050 if (LOGD) { 1051 Log.d(TAG, "allocated appWidgetId=" + appWidgetId 1052 + " for favoriteId=" + favoriteId); 1053 } 1054 values.clear(); 1055 values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_APPWIDGET); 1056 values.put(Favorites.APPWIDGET_ID, appWidgetId); 1057 1058 // Original widgets might not have valid spans when upgrading 1059 if (favoriteType == Favorites.ITEM_TYPE_WIDGET_SEARCH) { 1060 values.put(LauncherSettings.Favorites.SPANX, 4); 1061 values.put(LauncherSettings.Favorites.SPANY, 1); 1062 } else { 1063 values.put(LauncherSettings.Favorites.SPANX, 2); 1064 values.put(LauncherSettings.Favorites.SPANY, 2); 1065 } 1066 1067 String updateWhere = Favorites._ID + "=" + favoriteId; 1068 db.update(TABLE_FAVORITES, values, updateWhere, null); 1069 1070 if (favoriteType == Favorites.ITEM_TYPE_WIDGET_CLOCK) { 1071 // TODO: check return value 1072 appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, 1073 new ComponentName("com.android.alarmclock", 1074 "com.android.alarmclock.AnalogAppWidgetProvider")); 1075 } else if (favoriteType == Favorites.ITEM_TYPE_WIDGET_PHOTO_FRAME) { 1076 // TODO: check return value 1077 appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, 1078 new ComponentName("com.android.camera", 1079 "com.android.camera.PhotoAppWidgetProvider")); 1080 } else if (favoriteType == Favorites.ITEM_TYPE_WIDGET_SEARCH) { 1081 // TODO: check return value 1082 appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, 1083 getSearchWidgetProvider()); 1084 } 1085 } catch (RuntimeException ex) { 1086 Log.e(TAG, "Problem allocating appWidgetId", ex); 1087 } 1088 } 1089 1090 db.setTransactionSuccessful(); 1091 } catch (SQLException ex) { 1092 Log.w(TAG, "Problem while allocating appWidgetIds for existing widgets", ex); 1093 } finally { 1094 db.endTransaction(); 1095 if (c != null) { 1096 c.close(); 1097 } 1098 } 1099 1100 // Update max item id 1101 mMaxItemId = initializeMaxItemId(db); 1102 if (LOGD) Log.d(TAG, "mMaxItemId: " + mMaxItemId); 1103 } 1104 1105 private static final void beginDocument(XmlPullParser parser, String firstElementName) 1106 throws XmlPullParserException, IOException { 1107 int type; 1108 while ((type = parser.next()) != XmlPullParser.START_TAG 1109 && type != XmlPullParser.END_DOCUMENT) { 1110 ; 1111 } 1112 1113 if (type != XmlPullParser.START_TAG) { 1114 throw new XmlPullParserException("No start tag found"); 1115 } 1116 1117 if (!parser.getName().equals(firstElementName)) { 1118 throw new XmlPullParserException("Unexpected start tag: found " + parser.getName() + 1119 ", expected " + firstElementName); 1120 } 1121 } 1122 1123 /** 1124 * Loads the default set of favorite packages from an xml file. 1125 * 1126 * @param db The database to write the values into 1127 * @param filterContainerId The specific container id of items to load 1128 */ 1129 private int loadFavorites(SQLiteDatabase db, int workspaceResourceId) { 1130 Intent intent = new Intent(Intent.ACTION_MAIN, null); 1131 intent.addCategory(Intent.CATEGORY_LAUNCHER); 1132 ContentValues values = new ContentValues(); 1133 1134 if (LOGD) Log.v(TAG, String.format("Loading favorites from resid=0x%08x", workspaceResourceId)); 1135 1136 PackageManager packageManager = mContext.getPackageManager(); 1137 int i = 0; 1138 try { 1139 XmlResourceParser parser = mContext.getResources().getXml(workspaceResourceId); 1140 AttributeSet attrs = Xml.asAttributeSet(parser); 1141 beginDocument(parser, TAG_FAVORITES); 1142 1143 final int depth = parser.getDepth(); 1144 1145 int type; 1146 while (((type = parser.next()) != XmlPullParser.END_TAG || 1147 parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) { 1148 1149 if (type != XmlPullParser.START_TAG) { 1150 continue; 1151 } 1152 1153 boolean added = false; 1154 final String name = parser.getName(); 1155 1156 if (TAG_INCLUDE.equals(name)) { 1157 final TypedArray a = mContext.obtainStyledAttributes(attrs, R.styleable.Include); 1158 1159 final int resId = a.getResourceId(R.styleable.Include_workspace, 0); 1160 1161 if (LOGD) Log.v(TAG, String.format(("%" + (2*(depth+1)) + "s<include workspace=%08x>"), 1162 "", resId)); 1163 1164 if (resId != 0 && resId != workspaceResourceId) { 1165 // recursively load some more favorites, why not? 1166 i += loadFavorites(db, resId); 1167 added = false; 1168 } else { 1169 Log.w(TAG, String.format("Skipping <include workspace=0x%08x>", resId)); 1170 } 1171 1172 a.recycle(); 1173 1174 if (LOGD) Log.v(TAG, String.format(("%" + (2*(depth+1)) + "s</include>"), "")); 1175 continue; 1176 } 1177 1178 // Assuming it's a <favorite> at this point 1179 TypedArray a = mContext.obtainStyledAttributes(attrs, R.styleable.Favorite); 1180 1181 long container = LauncherSettings.Favorites.CONTAINER_DESKTOP; 1182 if (a.hasValue(R.styleable.Favorite_container)) { 1183 container = Long.valueOf(a.getString(R.styleable.Favorite_container)); 1184 } 1185 1186 String screen = a.getString(R.styleable.Favorite_screen); 1187 String x = a.getString(R.styleable.Favorite_x); 1188 String y = a.getString(R.styleable.Favorite_y); 1189 1190 values.clear(); 1191 values.put(LauncherSettings.Favorites.CONTAINER, container); 1192 values.put(LauncherSettings.Favorites.SCREEN, screen); 1193 values.put(LauncherSettings.Favorites.CELLX, x); 1194 values.put(LauncherSettings.Favorites.CELLY, y); 1195 1196 if (LOGD) { 1197 final String title = a.getString(R.styleable.Favorite_title); 1198 final String pkg = a.getString(R.styleable.Favorite_packageName); 1199 final String something = title != null ? title : pkg; 1200 Log.v(TAG, String.format( 1201 ("%" + (2*(depth+1)) + "s<%s%s c=%d s=%s x=%s y=%s>"), 1202 "", name, 1203 (something == null ? "" : (" \"" + something + "\"")), 1204 container, screen, x, y)); 1205 } 1206 1207 if (TAG_FAVORITE.equals(name)) { 1208 long id = addAppShortcut(db, values, a, packageManager, intent); 1209 added = id >= 0; 1210 } else if (TAG_SEARCH.equals(name)) { 1211 added = addSearchWidget(db, values); 1212 } else if (TAG_CLOCK.equals(name)) { 1213 added = addClockWidget(db, values); 1214 } else if (TAG_APPWIDGET.equals(name)) { 1215 added = addAppWidget(parser, attrs, type, db, values, a, packageManager); 1216 } else if (TAG_SHORTCUT.equals(name)) { 1217 long id = addUriShortcut(db, values, a); 1218 added = id >= 0; 1219 } else if (TAG_FOLDER.equals(name)) { 1220 String title; 1221 int titleResId = a.getResourceId(R.styleable.Favorite_title, -1); 1222 if (titleResId != -1) { 1223 title = mContext.getResources().getString(titleResId); 1224 } else { 1225 title = mContext.getResources().getString(R.string.folder_name); 1226 } 1227 values.put(LauncherSettings.Favorites.TITLE, title); 1228 long folderId = addFolder(db, values); 1229 added = folderId >= 0; 1230 1231 ArrayList<Long> folderItems = new ArrayList<Long>(); 1232 1233 int folderDepth = parser.getDepth(); 1234 while ((type = parser.next()) != XmlPullParser.END_TAG || 1235 parser.getDepth() > folderDepth) { 1236 if (type != XmlPullParser.START_TAG) { 1237 continue; 1238 } 1239 final String folder_item_name = parser.getName(); 1240 1241 TypedArray ar = mContext.obtainStyledAttributes(attrs, 1242 R.styleable.Favorite); 1243 values.clear(); 1244 values.put(LauncherSettings.Favorites.CONTAINER, folderId); 1245 1246 if (LOGD) { 1247 final String pkg = ar.getString(R.styleable.Favorite_packageName); 1248 final String uri = ar.getString(R.styleable.Favorite_uri); 1249 Log.v(TAG, String.format(("%" + (2*(folderDepth+1)) + "s<%s \"%s\">"), "", 1250 folder_item_name, uri != null ? uri : pkg)); 1251 } 1252 1253 if (TAG_FAVORITE.equals(folder_item_name) && folderId >= 0) { 1254 long id = 1255 addAppShortcut(db, values, ar, packageManager, intent); 1256 if (id >= 0) { 1257 folderItems.add(id); 1258 } 1259 } else if (TAG_SHORTCUT.equals(folder_item_name) && folderId >= 0) { 1260 long id = addUriShortcut(db, values, ar); 1261 if (id >= 0) { 1262 folderItems.add(id); 1263 } 1264 } else { 1265 throw new RuntimeException("Folders can " + 1266 "contain only shortcuts"); 1267 } 1268 ar.recycle(); 1269 } 1270 // We can only have folders with >= 2 items, so we need to remove the 1271 // folder and clean up if less than 2 items were included, or some 1272 // failed to add, and less than 2 were actually added 1273 if (folderItems.size() < 2 && folderId >= 0) { 1274 // We just delete the folder and any items that made it 1275 deleteId(db, folderId); 1276 if (folderItems.size() > 0) { 1277 deleteId(db, folderItems.get(0)); 1278 } 1279 added = false; 1280 } 1281 } 1282 if (added) i++; 1283 a.recycle(); 1284 } 1285 } catch (XmlPullParserException e) { 1286 Log.w(TAG, "Got exception parsing favorites.", e); 1287 } catch (IOException e) { 1288 Log.w(TAG, "Got exception parsing favorites.", e); 1289 } catch (RuntimeException e) { 1290 Log.w(TAG, "Got exception parsing favorites.", e); 1291 } 1292 1293 // Update the max item id after we have loaded the database 1294 if (mMaxItemId == -1) { 1295 mMaxItemId = initializeMaxItemId(db); 1296 } 1297 1298 return i; 1299 } 1300 1301 private long addAppShortcut(SQLiteDatabase db, ContentValues values, TypedArray a, 1302 PackageManager packageManager, Intent intent) { 1303 long id = -1; 1304 ActivityInfo info; 1305 String packageName = a.getString(R.styleable.Favorite_packageName); 1306 String className = a.getString(R.styleable.Favorite_className); 1307 try { 1308 ComponentName cn; 1309 try { 1310 cn = new ComponentName(packageName, className); 1311 info = packageManager.getActivityInfo(cn, 0); 1312 } catch (PackageManager.NameNotFoundException nnfe) { 1313 String[] packages = packageManager.currentToCanonicalPackageNames( 1314 new String[] { packageName }); 1315 cn = new ComponentName(packages[0], className); 1316 info = packageManager.getActivityInfo(cn, 0); 1317 } 1318 id = generateNewItemId(); 1319 intent.setComponent(cn); 1320 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | 1321 Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); 1322 values.put(Favorites.INTENT, intent.toUri(0)); 1323 values.put(Favorites.TITLE, info.loadLabel(packageManager).toString()); 1324 values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_APPLICATION); 1325 values.put(Favorites.SPANX, 1); 1326 values.put(Favorites.SPANY, 1); 1327 values.put(Favorites._ID, generateNewItemId()); 1328 if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values) < 0) { 1329 return -1; 1330 } 1331 } catch (PackageManager.NameNotFoundException e) { 1332 Log.w(TAG, "Unable to add favorite: " + packageName + 1333 "/" + className, e); 1334 } 1335 return id; 1336 } 1337 1338 private long addFolder(SQLiteDatabase db, ContentValues values) { 1339 values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_FOLDER); 1340 values.put(Favorites.SPANX, 1); 1341 values.put(Favorites.SPANY, 1); 1342 long id = generateNewItemId(); 1343 values.put(Favorites._ID, id); 1344 if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values) <= 0) { 1345 return -1; 1346 } else { 1347 return id; 1348 } 1349 } 1350 1351 private ComponentName getSearchWidgetProvider() { 1352 SearchManager searchManager = 1353 (SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE); 1354 ComponentName searchComponent = searchManager.getGlobalSearchActivity(); 1355 if (searchComponent == null) return null; 1356 return getProviderInPackage(searchComponent.getPackageName()); 1357 } 1358 1359 /** 1360 * Gets an appwidget provider from the given package. If the package contains more than 1361 * one appwidget provider, an arbitrary one is returned. 1362 */ 1363 private ComponentName getProviderInPackage(String packageName) { 1364 AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext); 1365 List<AppWidgetProviderInfo> providers = appWidgetManager.getInstalledProviders(); 1366 if (providers == null) return null; 1367 final int providerCount = providers.size(); 1368 for (int i = 0; i < providerCount; i++) { 1369 ComponentName provider = providers.get(i).provider; 1370 if (provider != null && provider.getPackageName().equals(packageName)) { 1371 return provider; 1372 } 1373 } 1374 return null; 1375 } 1376 1377 private boolean addSearchWidget(SQLiteDatabase db, ContentValues values) { 1378 ComponentName cn = getSearchWidgetProvider(); 1379 return addAppWidget(db, values, cn, 4, 1, null); 1380 } 1381 1382 private boolean addClockWidget(SQLiteDatabase db, ContentValues values) { 1383 ComponentName cn = new ComponentName("com.android.alarmclock", 1384 "com.android.alarmclock.AnalogAppWidgetProvider"); 1385 return addAppWidget(db, values, cn, 2, 2, null); 1386 } 1387 1388 private boolean addAppWidget(XmlResourceParser parser, AttributeSet attrs, int type, 1389 SQLiteDatabase db, ContentValues values, TypedArray a, 1390 PackageManager packageManager) throws XmlPullParserException, IOException { 1391 1392 String packageName = a.getString(R.styleable.Favorite_packageName); 1393 String className = a.getString(R.styleable.Favorite_className); 1394 1395 if (packageName == null || className == null) { 1396 return false; 1397 } 1398 1399 boolean hasPackage = true; 1400 ComponentName cn = new ComponentName(packageName, className); 1401 try { 1402 packageManager.getReceiverInfo(cn, 0); 1403 } catch (Exception e) { 1404 String[] packages = packageManager.currentToCanonicalPackageNames( 1405 new String[] { packageName }); 1406 cn = new ComponentName(packages[0], className); 1407 try { 1408 packageManager.getReceiverInfo(cn, 0); 1409 } catch (Exception e1) { 1410 hasPackage = false; 1411 } 1412 } 1413 1414 if (hasPackage) { 1415 int spanX = a.getInt(R.styleable.Favorite_spanX, 0); 1416 int spanY = a.getInt(R.styleable.Favorite_spanY, 0); 1417 1418 // Read the extras 1419 Bundle extras = new Bundle(); 1420 int widgetDepth = parser.getDepth(); 1421 while ((type = parser.next()) != XmlPullParser.END_TAG || 1422 parser.getDepth() > widgetDepth) { 1423 if (type != XmlPullParser.START_TAG) { 1424 continue; 1425 } 1426 1427 TypedArray ar = mContext.obtainStyledAttributes(attrs, R.styleable.Extra); 1428 if (TAG_EXTRA.equals(parser.getName())) { 1429 String key = ar.getString(R.styleable.Extra_key); 1430 String value = ar.getString(R.styleable.Extra_value); 1431 if (key != null && value != null) { 1432 extras.putString(key, value); 1433 } else { 1434 throw new RuntimeException("Widget extras must have a key and value"); 1435 } 1436 } else { 1437 throw new RuntimeException("Widgets can contain only extras"); 1438 } 1439 ar.recycle(); 1440 } 1441 1442 return addAppWidget(db, values, cn, spanX, spanY, extras); 1443 } 1444 1445 return false; 1446 } 1447 private boolean addAppWidget(SQLiteDatabase db, ContentValues values, ComponentName cn, 1448 int spanX, int spanY, Bundle extras) { 1449 boolean allocatedAppWidgets = false; 1450 final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext); 1451 1452 try { 1453 int appWidgetId = mAppWidgetHost.allocateAppWidgetId(); 1454 1455 values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_APPWIDGET); 1456 values.put(Favorites.SPANX, spanX); 1457 values.put(Favorites.SPANY, spanY); 1458 values.put(Favorites.APPWIDGET_ID, appWidgetId); 1459 values.put(Favorites.APPWIDGET_PROVIDER, cn.flattenToString()); 1460 values.put(Favorites._ID, generateNewItemId()); 1461 dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values); 1462 1463 allocatedAppWidgets = true; 1464 1465 // TODO: need to check return value 1466 appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, cn); 1467 1468 // Send a broadcast to configure the widget 1469 if (extras != null && !extras.isEmpty()) { 1470 Intent intent = new Intent(ACTION_APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE); 1471 intent.setComponent(cn); 1472 intent.putExtras(extras); 1473 intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); 1474 mContext.sendBroadcast(intent); 1475 } 1476 } catch (RuntimeException ex) { 1477 Log.e(TAG, "Problem allocating appWidgetId", ex); 1478 } 1479 1480 return allocatedAppWidgets; 1481 } 1482 1483 private long addUriShortcut(SQLiteDatabase db, ContentValues values, 1484 TypedArray a) { 1485 Resources r = mContext.getResources(); 1486 1487 final int iconResId = a.getResourceId(R.styleable.Favorite_icon, 0); 1488 final int titleResId = a.getResourceId(R.styleable.Favorite_title, 0); 1489 1490 Intent intent; 1491 String uri = null; 1492 try { 1493 uri = a.getString(R.styleable.Favorite_uri); 1494 intent = Intent.parseUri(uri, 0); 1495 } catch (URISyntaxException e) { 1496 Log.w(TAG, "Shortcut has malformed uri: " + uri); 1497 return -1; // Oh well 1498 } 1499 1500 if (iconResId == 0 || titleResId == 0) { 1501 Log.w(TAG, "Shortcut is missing title or icon resource ID"); 1502 return -1; 1503 } 1504 1505 long id = generateNewItemId(); 1506 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 1507 values.put(Favorites.INTENT, intent.toUri(0)); 1508 values.put(Favorites.TITLE, r.getString(titleResId)); 1509 values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_SHORTCUT); 1510 values.put(Favorites.SPANX, 1); 1511 values.put(Favorites.SPANY, 1); 1512 values.put(Favorites.ICON_TYPE, Favorites.ICON_TYPE_RESOURCE); 1513 values.put(Favorites.ICON_PACKAGE, mContext.getPackageName()); 1514 values.put(Favorites.ICON_RESOURCE, r.getResourceName(iconResId)); 1515 values.put(Favorites._ID, id); 1516 1517 if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values) < 0) { 1518 return -1; 1519 } 1520 return id; 1521 } 1522 1523 public void migrateLauncher2Shortcuts(SQLiteDatabase db, Uri uri) { 1524 final ContentResolver resolver = mContext.getContentResolver(); 1525 Cursor c = null; 1526 int count = 0; 1527 int curScreen = 0; 1528 1529 try { 1530 c = resolver.query(uri, null, null, null, "title ASC"); 1531 } catch (Exception e) { 1532 // Ignore 1533 } 1534 1535 // We already have a favorites database in the old provider 1536 if (c != null) { 1537 try { 1538 if (c.getCount() > 0) { 1539 final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID); 1540 final int intentIndex 1541 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.INTENT); 1542 final int titleIndex 1543 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE); 1544 final int iconTypeIndex 1545 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_TYPE); 1546 final int iconIndex 1547 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON); 1548 final int iconPackageIndex 1549 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_PACKAGE); 1550 final int iconResourceIndex 1551 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_RESOURCE); 1552 final int containerIndex 1553 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER); 1554 final int itemTypeIndex 1555 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE); 1556 final int screenIndex 1557 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN); 1558 final int cellXIndex 1559 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX); 1560 final int cellYIndex 1561 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY); 1562 final int uriIndex 1563 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.URI); 1564 final int displayModeIndex 1565 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.DISPLAY_MODE); 1566 1567 int i = 0; 1568 int curX = 0; 1569 int curY = 0; 1570 1571 final LauncherAppState app = LauncherAppState.getInstance(); 1572 final DeviceProfile grid = app.getDynamicGrid().getDeviceProfile(); 1573 final int width = (int) grid.numColumns; 1574 final int height = (int) grid.numRows; 1575 final int hotseatWidth = (int) grid.numHotseatIcons; 1576 PackageManager pm = mContext.getPackageManager(); 1577 1578 final HashSet<String> seenIntents = new HashSet<String>(c.getCount()); 1579 1580 final ArrayList<ContentValues> shortcuts = new ArrayList<ContentValues>(); 1581 final ArrayList<ContentValues> folders = new ArrayList<ContentValues>(); 1582 final SparseArray<ContentValues> hotseat = new SparseArray<ContentValues>(); 1583 1584 while (c.moveToNext()) { 1585 final int itemType = c.getInt(itemTypeIndex); 1586 if (itemType != Favorites.ITEM_TYPE_APPLICATION 1587 && itemType != Favorites.ITEM_TYPE_SHORTCUT 1588 && itemType != Favorites.ITEM_TYPE_FOLDER) { 1589 continue; 1590 } 1591 1592 final int cellX = c.getInt(cellXIndex); 1593 final int cellY = c.getInt(cellYIndex); 1594 final int screen = c.getInt(screenIndex); 1595 int container = c.getInt(containerIndex); 1596 final String intentStr = c.getString(intentIndex); 1597 Launcher.addDumpLog(TAG, "migrating \"" 1598 + c.getString(titleIndex) + "\" (" 1599 + cellX + "," + cellY + "@" 1600 + LauncherSettings.Favorites.containerToString(container) 1601 + "/" + screen 1602 + "): " + intentStr, true); 1603 1604 if (itemType != Favorites.ITEM_TYPE_FOLDER) { 1605 1606 final Intent intent; 1607 final ComponentName cn; 1608 try { 1609 intent = Intent.parseUri(intentStr, 0); 1610 } catch (URISyntaxException e) { 1611 // bogus intent? 1612 Launcher.addDumpLog(TAG, 1613 "skipping invalid intent uri", true); 1614 continue; 1615 } 1616 1617 cn = intent.getComponent(); 1618 if (TextUtils.isEmpty(intentStr)) { 1619 // no intent? no icon 1620 Launcher.addDumpLog(TAG, "skipping empty intent", true); 1621 continue; 1622 } else if (cn != null && 1623 !LauncherModel.isValidPackageComponent(pm, cn)) { 1624 // component no longer exists. 1625 Launcher.addDumpLog(TAG, "skipping item whose component " + 1626 "no longer exists.", true); 1627 continue; 1628 } else if (container == 1629 LauncherSettings.Favorites.CONTAINER_DESKTOP) { 1630 // Dedupe icons directly on the workspace 1631 1632 // Canonicalize 1633 // the Play Store sets the package parameter, but Launcher 1634 // does not, so we clear that out to keep them the same 1635 intent.setPackage(null); 1636 final String key = intent.toUri(0); 1637 if (seenIntents.contains(key)) { 1638 Launcher.addDumpLog(TAG, "skipping duplicate", true); 1639 continue; 1640 } else { 1641 seenIntents.add(key); 1642 } 1643 } 1644 } 1645 1646 ContentValues values = new ContentValues(c.getColumnCount()); 1647 values.put(LauncherSettings.Favorites._ID, c.getInt(idIndex)); 1648 values.put(LauncherSettings.Favorites.INTENT, intentStr); 1649 values.put(LauncherSettings.Favorites.TITLE, c.getString(titleIndex)); 1650 values.put(LauncherSettings.Favorites.ICON_TYPE, 1651 c.getInt(iconTypeIndex)); 1652 values.put(LauncherSettings.Favorites.ICON, c.getBlob(iconIndex)); 1653 values.put(LauncherSettings.Favorites.ICON_PACKAGE, 1654 c.getString(iconPackageIndex)); 1655 values.put(LauncherSettings.Favorites.ICON_RESOURCE, 1656 c.getString(iconResourceIndex)); 1657 values.put(LauncherSettings.Favorites.ITEM_TYPE, itemType); 1658 values.put(LauncherSettings.Favorites.APPWIDGET_ID, -1); 1659 values.put(LauncherSettings.Favorites.URI, c.getString(uriIndex)); 1660 values.put(LauncherSettings.Favorites.DISPLAY_MODE, 1661 c.getInt(displayModeIndex)); 1662 1663 if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { 1664 hotseat.put(screen, values); 1665 } 1666 1667 if (container != LauncherSettings.Favorites.CONTAINER_DESKTOP) { 1668 // In a folder or in the hotseat, preserve position 1669 values.put(LauncherSettings.Favorites.SCREEN, screen); 1670 values.put(LauncherSettings.Favorites.CELLX, cellX); 1671 values.put(LauncherSettings.Favorites.CELLY, cellY); 1672 } else { 1673 // For items contained directly on one of the workspace screen, 1674 // we'll determine their location (screen, x, y) in a second pass. 1675 } 1676 1677 values.put(LauncherSettings.Favorites.CONTAINER, container); 1678 1679 if (itemType != Favorites.ITEM_TYPE_FOLDER) { 1680 shortcuts.add(values); 1681 } else { 1682 folders.add(values); 1683 } 1684 } 1685 1686 // Now that we have all the hotseat icons, let's go through them left-right 1687 // and assign valid locations for them in the new hotseat 1688 final int N = hotseat.size(); 1689 for (int idx=0; idx<N; idx++) { 1690 int hotseatX = hotseat.keyAt(idx); 1691 ContentValues values = hotseat.valueAt(idx); 1692 1693 if (hotseatX == grid.hotseatAllAppsRank) { 1694 // let's drop this in the next available hole in the hotseat 1695 while (++hotseatX < hotseatWidth) { 1696 if (hotseat.get(hotseatX) == null) { 1697 // found a spot! move it here 1698 values.put(LauncherSettings.Favorites.SCREEN, 1699 hotseatX); 1700 break; 1701 } 1702 } 1703 } 1704 if (hotseatX >= hotseatWidth) { 1705 // no room for you in the hotseat? it's off to the desktop with you 1706 values.put(LauncherSettings.Favorites.CONTAINER, 1707 Favorites.CONTAINER_DESKTOP); 1708 } 1709 } 1710 1711 final ArrayList<ContentValues> allItems = new ArrayList<ContentValues>(); 1712 // Folders first 1713 allItems.addAll(folders); 1714 // Then shortcuts 1715 allItems.addAll(shortcuts); 1716 1717 // Layout all the folders 1718 for (ContentValues values: allItems) { 1719 if (values.getAsInteger(LauncherSettings.Favorites.CONTAINER) != 1720 LauncherSettings.Favorites.CONTAINER_DESKTOP) { 1721 // Hotseat items and folder items have already had their 1722 // location information set. Nothing to be done here. 1723 continue; 1724 } 1725 values.put(LauncherSettings.Favorites.SCREEN, curScreen); 1726 values.put(LauncherSettings.Favorites.CELLX, curX); 1727 values.put(LauncherSettings.Favorites.CELLY, curY); 1728 curX = (curX + 1) % width; 1729 if (curX == 0) { 1730 curY = (curY + 1); 1731 } 1732 // Leave the last row of icons blank on every screen 1733 if (curY == height - 1) { 1734 curScreen = (int) generateNewScreenId(); 1735 curY = 0; 1736 } 1737 } 1738 1739 if (allItems.size() > 0) { 1740 db.beginTransaction(); 1741 try { 1742 for (ContentValues row: allItems) { 1743 if (row == null) continue; 1744 if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, row) 1745 < 0) { 1746 return; 1747 } else { 1748 count++; 1749 } 1750 } 1751 db.setTransactionSuccessful(); 1752 } finally { 1753 db.endTransaction(); 1754 } 1755 } 1756 1757 db.beginTransaction(); 1758 try { 1759 for (i=0; i<=curScreen; i++) { 1760 final ContentValues values = new ContentValues(); 1761 values.put(LauncherSettings.WorkspaceScreens._ID, i); 1762 values.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, i); 1763 if (dbInsertAndCheck(this, db, TABLE_WORKSPACE_SCREENS, null, values) 1764 < 0) { 1765 return; 1766 } 1767 } 1768 db.setTransactionSuccessful(); 1769 } finally { 1770 db.endTransaction(); 1771 } 1772 } 1773 } finally { 1774 c.close(); 1775 } 1776 } 1777 1778 Launcher.addDumpLog(TAG, "migrated " + count + " icons from Launcher2 into " 1779 + (curScreen+1) + " screens", true); 1780 1781 // ensure that new screens are created to hold these icons 1782 setFlagJustLoadedOldDb(); 1783 1784 // Update max IDs; very important since we just grabbed IDs from another database 1785 mMaxItemId = initializeMaxItemId(db); 1786 mMaxScreenId = initializeMaxScreenId(db); 1787 if (LOGD) Log.d(TAG, "mMaxItemId: " + mMaxItemId + " mMaxScreenId: " + mMaxScreenId); 1788 } 1789 } 1790 1791 /** 1792 * Build a query string that will match any row where the column matches 1793 * anything in the values list. 1794 */ 1795 static String buildOrWhereString(String column, int[] values) { 1796 StringBuilder selectWhere = new StringBuilder(); 1797 for (int i = values.length - 1; i >= 0; i--) { 1798 selectWhere.append(column).append("=").append(values[i]); 1799 if (i > 0) { 1800 selectWhere.append(" OR "); 1801 } 1802 } 1803 return selectWhere.toString(); 1804 } 1805 1806 static class SqlArguments { 1807 public final String table; 1808 public final String where; 1809 public final String[] args; 1810 1811 SqlArguments(Uri url, String where, String[] args) { 1812 if (url.getPathSegments().size() == 1) { 1813 this.table = url.getPathSegments().get(0); 1814 this.where = where; 1815 this.args = args; 1816 } else if (url.getPathSegments().size() != 2) { 1817 throw new IllegalArgumentException("Invalid URI: " + url); 1818 } else if (!TextUtils.isEmpty(where)) { 1819 throw new UnsupportedOperationException("WHERE clause not supported: " + url); 1820 } else { 1821 this.table = url.getPathSegments().get(0); 1822 this.where = "_id=" + ContentUris.parseId(url); 1823 this.args = null; 1824 } 1825 } 1826 1827 SqlArguments(Uri url) { 1828 if (url.getPathSegments().size() == 1) { 1829 table = url.getPathSegments().get(0); 1830 where = null; 1831 args = null; 1832 } else { 1833 throw new IllegalArgumentException("Invalid URI: " + url); 1834 } 1835 } 1836 } 1837 } 1838