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.ApplicationInfo; 36 import android.content.pm.PackageManager; 37 import android.content.pm.ResolveInfo; 38 import android.content.res.Resources; 39 import android.content.res.XmlResourceParser; 40 import android.database.Cursor; 41 import android.database.SQLException; 42 import android.database.sqlite.SQLiteDatabase; 43 import android.database.sqlite.SQLiteOpenHelper; 44 import android.database.sqlite.SQLiteQueryBuilder; 45 import android.database.sqlite.SQLiteStatement; 46 import android.graphics.Bitmap; 47 import android.graphics.BitmapFactory; 48 import android.net.Uri; 49 import android.os.Bundle; 50 import android.provider.Settings; 51 import android.text.TextUtils; 52 import android.util.Log; 53 import android.util.SparseArray; 54 55 import com.android.launcher3.AutoInstallsLayout.LayoutParserCallback; 56 import com.android.launcher3.LauncherSettings.Favorites; 57 import com.android.launcher3.compat.UserHandleCompat; 58 import com.android.launcher3.compat.UserManagerCompat; 59 import com.android.launcher3.config.ProviderConfig; 60 61 import org.xmlpull.v1.XmlPullParser; 62 import org.xmlpull.v1.XmlPullParserException; 63 64 import java.io.File; 65 import java.io.IOException; 66 import java.net.URISyntaxException; 67 import java.util.ArrayList; 68 import java.util.Collections; 69 import java.util.HashSet; 70 import java.util.List; 71 72 public class LauncherProvider extends ContentProvider { 73 private static final String TAG = "Launcher.LauncherProvider"; 74 private static final boolean LOGD = false; 75 76 private static final String DATABASE_NAME = "launcher.db"; 77 78 private static final int DATABASE_VERSION = 20; 79 80 static final String OLD_AUTHORITY = "com.android.launcher2.settings"; 81 static final String AUTHORITY = ProviderConfig.AUTHORITY; 82 83 // Should we attempt to load anything from the com.android.launcher2 provider? 84 static final boolean IMPORT_LAUNCHER2_DATABASE = false; 85 86 static final String TABLE_FAVORITES = "favorites"; 87 static final String TABLE_WORKSPACE_SCREENS = "workspaceScreens"; 88 static final String PARAMETER_NOTIFY = "notify"; 89 static final String UPGRADED_FROM_OLD_DATABASE = 90 "UPGRADED_FROM_OLD_DATABASE"; 91 static final String EMPTY_DATABASE_CREATED = 92 "EMPTY_DATABASE_CREATED"; 93 94 private static final String ACTION_APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE = 95 "com.android.launcher.action.APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE"; 96 97 private static final String URI_PARAM_IS_EXTERNAL_ADD = "isExternalAdd"; 98 99 private LauncherProviderChangeListener mListener; 100 101 /** 102 * {@link Uri} triggered at any registered {@link android.database.ContentObserver} when 103 * {@link AppWidgetHost#deleteHost()} is called during database creation. 104 * Use this to recall {@link AppWidgetHost#startListening()} if needed. 105 */ 106 static final Uri CONTENT_APPWIDGET_RESET_URI = 107 Uri.parse("content://" + AUTHORITY + "/appWidgetReset"); 108 109 private DatabaseHelper mOpenHelper; 110 private static boolean sJustLoadedFromOldDb; 111 112 @Override onCreate()113 public boolean onCreate() { 114 final Context context = getContext(); 115 mOpenHelper = new DatabaseHelper(context); 116 LauncherAppState.setLauncherProvider(this); 117 return true; 118 } 119 wasNewDbCreated()120 public boolean wasNewDbCreated() { 121 return mOpenHelper.wasNewDbCreated(); 122 } 123 setLauncherProviderChangeListener(LauncherProviderChangeListener listener)124 public void setLauncherProviderChangeListener(LauncherProviderChangeListener listener) { 125 mListener = listener; 126 } 127 128 @Override getType(Uri uri)129 public String getType(Uri uri) { 130 SqlArguments args = new SqlArguments(uri, null, null); 131 if (TextUtils.isEmpty(args.where)) { 132 return "vnd.android.cursor.dir/" + args.table; 133 } else { 134 return "vnd.android.cursor.item/" + args.table; 135 } 136 } 137 138 @Override query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)139 public Cursor query(Uri uri, String[] projection, String selection, 140 String[] selectionArgs, String sortOrder) { 141 142 SqlArguments args = new SqlArguments(uri, selection, selectionArgs); 143 SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); 144 qb.setTables(args.table); 145 146 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 147 Cursor result = qb.query(db, projection, args.where, args.args, null, null, sortOrder); 148 result.setNotificationUri(getContext().getContentResolver(), uri); 149 150 return result; 151 } 152 dbInsertAndCheck(DatabaseHelper helper, SQLiteDatabase db, String table, String nullColumnHack, ContentValues values)153 private static long dbInsertAndCheck(DatabaseHelper helper, 154 SQLiteDatabase db, String table, String nullColumnHack, ContentValues values) { 155 if (values == null) { 156 throw new RuntimeException("Error: attempting to insert null values"); 157 } 158 if (!values.containsKey(LauncherSettings.ChangeLogColumns._ID)) { 159 throw new RuntimeException("Error: attempting to add item without specifying an id"); 160 } 161 helper.checkId(table, values); 162 return db.insert(table, nullColumnHack, values); 163 } 164 deleteId(SQLiteDatabase db, long id)165 private static void deleteId(SQLiteDatabase db, long id) { 166 Uri uri = LauncherSettings.Favorites.getContentUri(id, false); 167 SqlArguments args = new SqlArguments(uri, null, null); 168 db.delete(args.table, args.where, args.args); 169 } 170 171 @Override insert(Uri uri, ContentValues initialValues)172 public Uri insert(Uri uri, ContentValues initialValues) { 173 SqlArguments args = new SqlArguments(uri); 174 175 // In very limited cases, we support system|signature permission apps to add to the db 176 String externalAdd = uri.getQueryParameter(URI_PARAM_IS_EXTERNAL_ADD); 177 if (externalAdd != null && "true".equals(externalAdd)) { 178 if (!mOpenHelper.initializeExternalAdd(initialValues)) { 179 return null; 180 } 181 } 182 183 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 184 addModifiedTime(initialValues); 185 final long rowId = dbInsertAndCheck(mOpenHelper, db, args.table, null, initialValues); 186 if (rowId <= 0) return null; 187 188 uri = ContentUris.withAppendedId(uri, rowId); 189 sendNotify(uri); 190 191 return uri; 192 } 193 194 195 @Override bulkInsert(Uri uri, ContentValues[] values)196 public int bulkInsert(Uri uri, ContentValues[] values) { 197 SqlArguments args = new SqlArguments(uri); 198 199 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 200 db.beginTransaction(); 201 try { 202 int numValues = values.length; 203 for (int i = 0; i < numValues; i++) { 204 addModifiedTime(values[i]); 205 if (dbInsertAndCheck(mOpenHelper, db, args.table, null, values[i]) < 0) { 206 return 0; 207 } 208 } 209 db.setTransactionSuccessful(); 210 } finally { 211 db.endTransaction(); 212 } 213 214 sendNotify(uri); 215 return values.length; 216 } 217 218 @Override applyBatch(ArrayList<ContentProviderOperation> operations)219 public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations) 220 throws OperationApplicationException { 221 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 222 db.beginTransaction(); 223 try { 224 ContentProviderResult[] result = super.applyBatch(operations); 225 db.setTransactionSuccessful(); 226 return result; 227 } finally { 228 db.endTransaction(); 229 } 230 } 231 232 @Override delete(Uri uri, String selection, String[] selectionArgs)233 public int delete(Uri uri, String selection, String[] selectionArgs) { 234 SqlArguments args = new SqlArguments(uri, selection, selectionArgs); 235 236 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 237 int count = db.delete(args.table, args.where, args.args); 238 if (count > 0) sendNotify(uri); 239 240 return count; 241 } 242 243 @Override update(Uri uri, ContentValues values, String selection, String[] selectionArgs)244 public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { 245 SqlArguments args = new SqlArguments(uri, selection, selectionArgs); 246 247 addModifiedTime(values); 248 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 249 int count = db.update(args.table, values, args.where, args.args); 250 if (count > 0) sendNotify(uri); 251 252 return count; 253 } 254 sendNotify(Uri uri)255 private void sendNotify(Uri uri) { 256 String notify = uri.getQueryParameter(PARAMETER_NOTIFY); 257 if (notify == null || "true".equals(notify)) { 258 getContext().getContentResolver().notifyChange(uri, null); 259 } 260 261 // always notify the backup agent 262 LauncherBackupAgentHelper.dataChanged(getContext()); 263 if (mListener != null) { 264 mListener.onLauncherProviderChange(); 265 } 266 } 267 addModifiedTime(ContentValues values)268 private void addModifiedTime(ContentValues values) { 269 values.put(LauncherSettings.ChangeLogColumns.MODIFIED, System.currentTimeMillis()); 270 } 271 generateNewItemId()272 public long generateNewItemId() { 273 return mOpenHelper.generateNewItemId(); 274 } 275 updateMaxItemId(long id)276 public void updateMaxItemId(long id) { 277 mOpenHelper.updateMaxItemId(id); 278 } 279 generateNewScreenId()280 public long generateNewScreenId() { 281 return mOpenHelper.generateNewScreenId(); 282 } 283 284 // This is only required one time while loading the workspace during the 285 // upgrade path, and should never be called from anywhere else. updateMaxScreenId(long maxScreenId)286 public void updateMaxScreenId(long maxScreenId) { 287 mOpenHelper.updateMaxScreenId(maxScreenId); 288 } 289 290 /** 291 * @param Should we load the old db for upgrade? first run only. 292 */ justLoadedOldDb()293 synchronized public boolean justLoadedOldDb() { 294 String spKey = LauncherAppState.getSharedPreferencesKey(); 295 SharedPreferences sp = getContext().getSharedPreferences(spKey, Context.MODE_PRIVATE); 296 297 boolean loadedOldDb = false || sJustLoadedFromOldDb; 298 299 sJustLoadedFromOldDb = false; 300 if (sp.getBoolean(UPGRADED_FROM_OLD_DATABASE, false)) { 301 302 SharedPreferences.Editor editor = sp.edit(); 303 editor.remove(UPGRADED_FROM_OLD_DATABASE); 304 editor.commit(); 305 loadedOldDb = true; 306 } 307 return loadedOldDb; 308 } 309 310 /** 311 * Clears all the data for a fresh start. 312 */ createEmptyDB()313 synchronized public void createEmptyDB() { 314 mOpenHelper.createEmptyDB(mOpenHelper.getWritableDatabase()); 315 } 316 317 /** 318 * Loads the default workspace based on the following priority scheme: 319 * 1) From a package provided by play store 320 * 2) From a partner configuration APK, already in the system image 321 * 3) The default configuration for the particular device 322 */ loadDefaultFavoritesIfNecessary()323 synchronized public void loadDefaultFavoritesIfNecessary() { 324 String spKey = LauncherAppState.getSharedPreferencesKey(); 325 SharedPreferences sp = getContext().getSharedPreferences(spKey, Context.MODE_PRIVATE); 326 327 if (sp.getBoolean(EMPTY_DATABASE_CREATED, false)) { 328 Log.d(TAG, "loading default workspace"); 329 330 WorkspaceLoader loader = AutoInstallsLayout.get(getContext(), 331 mOpenHelper.mAppWidgetHost, mOpenHelper); 332 333 if (loader == null) { 334 final Partner partner = Partner.get(getContext().getPackageManager()); 335 if (partner != null && partner.hasDefaultLayout()) { 336 final Resources partnerRes = partner.getResources(); 337 int workspaceResId = partnerRes.getIdentifier(Partner.RES_DEFAULT_LAYOUT, 338 "xml", partner.getPackageName()); 339 if (workspaceResId != 0) { 340 loader = new SimpleWorkspaceLoader(mOpenHelper, partnerRes, workspaceResId); 341 } 342 } 343 } 344 345 if (loader == null) { 346 loader = new SimpleWorkspaceLoader(mOpenHelper, getContext().getResources(), 347 getDefaultWorkspaceResourceId()); 348 } 349 350 // Populate favorites table with initial favorites 351 SharedPreferences.Editor editor = sp.edit().remove(EMPTY_DATABASE_CREATED); 352 mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(), loader); 353 editor.commit(); 354 } 355 } 356 migrateLauncher2Shortcuts()357 public void migrateLauncher2Shortcuts() { 358 mOpenHelper.migrateLauncher2Shortcuts(mOpenHelper.getWritableDatabase(), 359 Uri.parse(getContext().getString(R.string.old_launcher_provider_uri))); 360 } 361 getDefaultWorkspaceResourceId()362 private static int getDefaultWorkspaceResourceId() { 363 LauncherAppState app = LauncherAppState.getInstance(); 364 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile(); 365 if (LauncherAppState.isDisableAllApps()) { 366 return grid.defaultNoAllAppsLayoutId; 367 } else { 368 return grid.defaultLayoutId; 369 } 370 } 371 372 private static interface ContentValuesCallback { onRow(ContentValues values)373 public void onRow(ContentValues values); 374 } 375 shouldImportLauncher2Database(Context context)376 private static boolean shouldImportLauncher2Database(Context context) { 377 boolean isTablet = context.getResources().getBoolean(R.bool.is_tablet); 378 379 // We don't import the old databse for tablets, as the grid size has changed. 380 return !isTablet && IMPORT_LAUNCHER2_DATABASE; 381 } 382 deleteDatabase()383 public void deleteDatabase() { 384 // Are you sure? (y/n) 385 final SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 386 final File dbFile = new File(db.getPath()); 387 mOpenHelper.close(); 388 if (dbFile.exists()) { 389 SQLiteDatabase.deleteDatabase(dbFile); 390 } 391 mOpenHelper = new DatabaseHelper(getContext()); 392 } 393 394 private static class DatabaseHelper extends SQLiteOpenHelper implements LayoutParserCallback { 395 private static final String TAG_RESOLVE = "resolve"; 396 private static final String TAG_FAVORITES = "favorites"; 397 private static final String TAG_FAVORITE = "favorite"; 398 private static final String TAG_APPWIDGET = "appwidget"; 399 private static final String TAG_SHORTCUT = "shortcut"; 400 private static final String TAG_FOLDER = "folder"; 401 private static final String TAG_PARTNER_FOLDER = "partner-folder"; 402 private static final String TAG_EXTRA = "extra"; 403 private static final String TAG_INCLUDE = "include"; 404 405 // Style attrs -- "Favorite" 406 private static final String ATTR_CLASS_NAME = "className"; 407 private static final String ATTR_PACKAGE_NAME = "packageName"; 408 private static final String ATTR_CONTAINER = "container"; 409 private static final String ATTR_SCREEN = "screen"; 410 private static final String ATTR_X = "x"; 411 private static final String ATTR_Y = "y"; 412 private static final String ATTR_SPAN_X = "spanX"; 413 private static final String ATTR_SPAN_Y = "spanY"; 414 private static final String ATTR_ICON = "icon"; 415 private static final String ATTR_TITLE = "title"; 416 private static final String ATTR_URI = "uri"; 417 418 // Style attrs -- "Include" 419 private static final String ATTR_WORKSPACE = "workspace"; 420 421 // Style attrs -- "Extra" 422 private static final String ATTR_KEY = "key"; 423 private static final String ATTR_VALUE = "value"; 424 425 private final Context mContext; 426 private final PackageManager mPackageManager; 427 private final AppWidgetHost mAppWidgetHost; 428 private long mMaxItemId = -1; 429 private long mMaxScreenId = -1; 430 431 private boolean mNewDbCreated = false; 432 DatabaseHelper(Context context)433 DatabaseHelper(Context context) { 434 super(context, DATABASE_NAME, null, DATABASE_VERSION); 435 mContext = context; 436 mPackageManager = context.getPackageManager(); 437 mAppWidgetHost = new AppWidgetHost(context, Launcher.APPWIDGET_HOST_ID); 438 439 // In the case where neither onCreate nor onUpgrade gets called, we read the maxId from 440 // the DB here 441 if (mMaxItemId == -1) { 442 mMaxItemId = initializeMaxItemId(getWritableDatabase()); 443 } 444 if (mMaxScreenId == -1) { 445 mMaxScreenId = initializeMaxScreenId(getWritableDatabase()); 446 } 447 } 448 wasNewDbCreated()449 public boolean wasNewDbCreated() { 450 return mNewDbCreated; 451 } 452 453 /** 454 * Send notification that we've deleted the {@link AppWidgetHost}, 455 * probably as part of the initial database creation. The receiver may 456 * want to re-call {@link AppWidgetHost#startListening()} to ensure 457 * callbacks are correctly set. 458 */ sendAppWidgetResetNotify()459 private void sendAppWidgetResetNotify() { 460 final ContentResolver resolver = mContext.getContentResolver(); 461 resolver.notifyChange(CONTENT_APPWIDGET_RESET_URI, null); 462 } 463 464 @Override onCreate(SQLiteDatabase db)465 public void onCreate(SQLiteDatabase db) { 466 if (LOGD) Log.d(TAG, "creating new launcher database"); 467 468 mMaxItemId = 1; 469 mMaxScreenId = 0; 470 mNewDbCreated = true; 471 472 UserManagerCompat userManager = UserManagerCompat.getInstance(mContext); 473 long userSerialNumber = userManager.getSerialNumberForUser( 474 UserHandleCompat.myUserHandle()); 475 476 db.execSQL("CREATE TABLE favorites (" + 477 "_id INTEGER PRIMARY KEY," + 478 "title TEXT," + 479 "intent TEXT," + 480 "container INTEGER," + 481 "screen INTEGER," + 482 "cellX INTEGER," + 483 "cellY INTEGER," + 484 "spanX INTEGER," + 485 "spanY INTEGER," + 486 "itemType INTEGER," + 487 "appWidgetId INTEGER NOT NULL DEFAULT -1," + 488 "isShortcut INTEGER," + 489 "iconType INTEGER," + 490 "iconPackage TEXT," + 491 "iconResource TEXT," + 492 "icon BLOB," + 493 "uri TEXT," + 494 "displayMode INTEGER," + 495 "appWidgetProvider TEXT," + 496 "modified INTEGER NOT NULL DEFAULT 0," + 497 "restored INTEGER NOT NULL DEFAULT 0," + 498 "profileId INTEGER DEFAULT " + userSerialNumber + 499 ");"); 500 addWorkspacesTable(db); 501 502 // Database was just created, so wipe any previous widgets 503 if (mAppWidgetHost != null) { 504 mAppWidgetHost.deleteHost(); 505 sendAppWidgetResetNotify(); 506 } 507 508 if (shouldImportLauncher2Database(mContext)) { 509 // Try converting the old database 510 ContentValuesCallback permuteScreensCb = new ContentValuesCallback() { 511 public void onRow(ContentValues values) { 512 int container = values.getAsInteger(LauncherSettings.Favorites.CONTAINER); 513 if (container == Favorites.CONTAINER_DESKTOP) { 514 int screen = values.getAsInteger(LauncherSettings.Favorites.SCREEN); 515 screen = (int) upgradeLauncherDb_permuteScreens(screen); 516 values.put(LauncherSettings.Favorites.SCREEN, screen); 517 } 518 } 519 }; 520 Uri uri = Uri.parse("content://" + Settings.AUTHORITY + 521 "/old_favorites?notify=true"); 522 if (!convertDatabase(db, uri, permuteScreensCb, true)) { 523 // Try and upgrade from the Launcher2 db 524 uri = Uri.parse(mContext.getString(R.string.old_launcher_provider_uri)); 525 if (!convertDatabase(db, uri, permuteScreensCb, false)) { 526 // If we fail, then set a flag to load the default workspace 527 setFlagEmptyDbCreated(); 528 return; 529 } 530 } 531 // Right now, in non-default workspace cases, we want to run the final 532 // upgrade code (ie. to fix workspace screen indices -> ids, etc.), so 533 // set that flag too. 534 setFlagJustLoadedOldDb(); 535 } else { 536 // Fresh and clean launcher DB. 537 mMaxItemId = initializeMaxItemId(db); 538 setFlagEmptyDbCreated(); 539 } 540 } 541 addWorkspacesTable(SQLiteDatabase db)542 private void addWorkspacesTable(SQLiteDatabase db) { 543 db.execSQL("CREATE TABLE " + TABLE_WORKSPACE_SCREENS + " (" + 544 LauncherSettings.WorkspaceScreens._ID + " INTEGER," + 545 LauncherSettings.WorkspaceScreens.SCREEN_RANK + " INTEGER," + 546 LauncherSettings.ChangeLogColumns.MODIFIED + " INTEGER NOT NULL DEFAULT 0" + 547 ");"); 548 } 549 removeOrphanedItems(SQLiteDatabase db)550 private void removeOrphanedItems(SQLiteDatabase db) { 551 // Delete items directly on the workspace who's screen id doesn't exist 552 // "DELETE FROM favorites WHERE screen NOT IN (SELECT _id FROM workspaceScreens) 553 // AND container = -100" 554 String removeOrphanedDesktopItems = "DELETE FROM " + TABLE_FAVORITES + 555 " WHERE " + 556 LauncherSettings.Favorites.SCREEN + " NOT IN (SELECT " + 557 LauncherSettings.WorkspaceScreens._ID + " FROM " + TABLE_WORKSPACE_SCREENS + ")" + 558 " AND " + 559 LauncherSettings.Favorites.CONTAINER + " = " + 560 LauncherSettings.Favorites.CONTAINER_DESKTOP; 561 db.execSQL(removeOrphanedDesktopItems); 562 563 // Delete items contained in folders which no longer exist (after above statement) 564 // "DELETE FROM favorites WHERE container <> -100 AND container <> -101 AND container 565 // NOT IN (SELECT _id FROM favorites WHERE itemType = 2)" 566 String removeOrphanedFolderItems = "DELETE FROM " + TABLE_FAVORITES + 567 " WHERE " + 568 LauncherSettings.Favorites.CONTAINER + " <> " + 569 LauncherSettings.Favorites.CONTAINER_DESKTOP + 570 " AND " 571 + LauncherSettings.Favorites.CONTAINER + " <> " + 572 LauncherSettings.Favorites.CONTAINER_HOTSEAT + 573 " AND " 574 + LauncherSettings.Favorites.CONTAINER + " NOT IN (SELECT " + 575 LauncherSettings.Favorites._ID + " FROM " + TABLE_FAVORITES + 576 " WHERE " + LauncherSettings.Favorites.ITEM_TYPE + " = " + 577 LauncherSettings.Favorites.ITEM_TYPE_FOLDER + ")"; 578 db.execSQL(removeOrphanedFolderItems); 579 } 580 setFlagJustLoadedOldDb()581 private void setFlagJustLoadedOldDb() { 582 String spKey = LauncherAppState.getSharedPreferencesKey(); 583 SharedPreferences sp = mContext.getSharedPreferences(spKey, Context.MODE_PRIVATE); 584 SharedPreferences.Editor editor = sp.edit(); 585 editor.putBoolean(UPGRADED_FROM_OLD_DATABASE, true); 586 editor.putBoolean(EMPTY_DATABASE_CREATED, false); 587 editor.commit(); 588 } 589 setFlagEmptyDbCreated()590 private void setFlagEmptyDbCreated() { 591 String spKey = LauncherAppState.getSharedPreferencesKey(); 592 SharedPreferences sp = mContext.getSharedPreferences(spKey, Context.MODE_PRIVATE); 593 SharedPreferences.Editor editor = sp.edit(); 594 editor.putBoolean(EMPTY_DATABASE_CREATED, true); 595 editor.putBoolean(UPGRADED_FROM_OLD_DATABASE, false); 596 editor.commit(); 597 } 598 599 // We rearrange the screens from the old launcher 600 // 12345 -> 34512 upgradeLauncherDb_permuteScreens(long screen)601 private long upgradeLauncherDb_permuteScreens(long screen) { 602 if (screen >= 2) { 603 return screen - 2; 604 } else { 605 return screen + 3; 606 } 607 } 608 convertDatabase(SQLiteDatabase db, Uri uri, ContentValuesCallback cb, boolean deleteRows)609 private boolean convertDatabase(SQLiteDatabase db, Uri uri, 610 ContentValuesCallback cb, boolean deleteRows) { 611 if (LOGD) Log.d(TAG, "converting database from an older format, but not onUpgrade"); 612 boolean converted = false; 613 614 final ContentResolver resolver = mContext.getContentResolver(); 615 Cursor cursor = null; 616 617 try { 618 cursor = resolver.query(uri, null, null, null, null); 619 } catch (Exception e) { 620 // Ignore 621 } 622 623 // We already have a favorites database in the old provider 624 if (cursor != null) { 625 try { 626 if (cursor.getCount() > 0) { 627 converted = copyFromCursor(db, cursor, cb) > 0; 628 if (converted && deleteRows) { 629 resolver.delete(uri, null, null); 630 } 631 } 632 } finally { 633 cursor.close(); 634 } 635 } 636 637 if (converted) { 638 // Convert widgets from this import into widgets 639 if (LOGD) Log.d(TAG, "converted and now triggering widget upgrade"); 640 convertWidgets(db); 641 642 // Update max item id 643 mMaxItemId = initializeMaxItemId(db); 644 if (LOGD) Log.d(TAG, "mMaxItemId: " + mMaxItemId); 645 } 646 647 return converted; 648 } 649 copyFromCursor(SQLiteDatabase db, Cursor c, ContentValuesCallback cb)650 private int copyFromCursor(SQLiteDatabase db, Cursor c, ContentValuesCallback cb) { 651 final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID); 652 final int intentIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.INTENT); 653 final int titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE); 654 final int iconTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_TYPE); 655 final int iconIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON); 656 final int iconPackageIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_PACKAGE); 657 final int iconResourceIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_RESOURCE); 658 final int containerIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER); 659 final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE); 660 final int screenIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN); 661 final int cellXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX); 662 final int cellYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY); 663 final int uriIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.URI); 664 final int displayModeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.DISPLAY_MODE); 665 666 ContentValues[] rows = new ContentValues[c.getCount()]; 667 int i = 0; 668 while (c.moveToNext()) { 669 ContentValues values = new ContentValues(c.getColumnCount()); 670 values.put(LauncherSettings.Favorites._ID, c.getLong(idIndex)); 671 values.put(LauncherSettings.Favorites.INTENT, c.getString(intentIndex)); 672 values.put(LauncherSettings.Favorites.TITLE, c.getString(titleIndex)); 673 values.put(LauncherSettings.Favorites.ICON_TYPE, c.getInt(iconTypeIndex)); 674 values.put(LauncherSettings.Favorites.ICON, c.getBlob(iconIndex)); 675 values.put(LauncherSettings.Favorites.ICON_PACKAGE, c.getString(iconPackageIndex)); 676 values.put(LauncherSettings.Favorites.ICON_RESOURCE, c.getString(iconResourceIndex)); 677 values.put(LauncherSettings.Favorites.CONTAINER, c.getInt(containerIndex)); 678 values.put(LauncherSettings.Favorites.ITEM_TYPE, c.getInt(itemTypeIndex)); 679 values.put(LauncherSettings.Favorites.APPWIDGET_ID, -1); 680 values.put(LauncherSettings.Favorites.SCREEN, c.getInt(screenIndex)); 681 values.put(LauncherSettings.Favorites.CELLX, c.getInt(cellXIndex)); 682 values.put(LauncherSettings.Favorites.CELLY, c.getInt(cellYIndex)); 683 values.put(LauncherSettings.Favorites.URI, c.getString(uriIndex)); 684 values.put(LauncherSettings.Favorites.DISPLAY_MODE, c.getInt(displayModeIndex)); 685 if (cb != null) { 686 cb.onRow(values); 687 } 688 rows[i++] = values; 689 } 690 691 int total = 0; 692 if (i > 0) { 693 db.beginTransaction(); 694 try { 695 int numValues = rows.length; 696 for (i = 0; i < numValues; i++) { 697 if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, rows[i]) < 0) { 698 return 0; 699 } else { 700 total++; 701 } 702 } 703 db.setTransactionSuccessful(); 704 } finally { 705 db.endTransaction(); 706 } 707 } 708 709 return total; 710 } 711 712 @Override onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)713 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 714 if (LOGD) Log.d(TAG, "onUpgrade triggered: " + oldVersion); 715 716 int version = oldVersion; 717 if (version < 3) { 718 // upgrade 1,2 -> 3 added appWidgetId column 719 db.beginTransaction(); 720 try { 721 // Insert new column for holding appWidgetIds 722 db.execSQL("ALTER TABLE favorites " + 723 "ADD COLUMN appWidgetId INTEGER NOT NULL DEFAULT -1;"); 724 db.setTransactionSuccessful(); 725 version = 3; 726 } catch (SQLException ex) { 727 // Old version remains, which means we wipe old data 728 Log.e(TAG, ex.getMessage(), ex); 729 } finally { 730 db.endTransaction(); 731 } 732 733 // Convert existing widgets only if table upgrade was successful 734 if (version == 3) { 735 convertWidgets(db); 736 } 737 } 738 739 if (version < 4) { 740 version = 4; 741 } 742 743 // Where's version 5? 744 // - Donut and sholes on 2.0 shipped with version 4 of launcher1. 745 // - Passion shipped on 2.1 with version 6 of launcher3 746 // - Sholes shipped on 2.1r1 (aka Mr. 3) with version 5 of launcher 1 747 // but version 5 on there was the updateContactsShortcuts change 748 // which was version 6 in launcher 2 (first shipped on passion 2.1r1). 749 // The updateContactsShortcuts change is idempotent, so running it twice 750 // is okay so we'll do that when upgrading the devices that shipped with it. 751 if (version < 6) { 752 // We went from 3 to 5 screens. Move everything 1 to the right 753 db.beginTransaction(); 754 try { 755 db.execSQL("UPDATE favorites SET screen=(screen + 1);"); 756 db.setTransactionSuccessful(); 757 } catch (SQLException ex) { 758 // Old version remains, which means we wipe old data 759 Log.e(TAG, ex.getMessage(), ex); 760 } finally { 761 db.endTransaction(); 762 } 763 764 // We added the fast track. 765 if (updateContactsShortcuts(db)) { 766 version = 6; 767 } 768 } 769 770 if (version < 7) { 771 // Version 7 gets rid of the special search widget. 772 convertWidgets(db); 773 version = 7; 774 } 775 776 if (version < 8) { 777 // Version 8 (froyo) has the icons all normalized. This should 778 // already be the case in practice, but we now rely on it and don't 779 // resample the images each time. 780 normalizeIcons(db); 781 version = 8; 782 } 783 784 if (version < 9) { 785 // The max id is not yet set at this point (onUpgrade is triggered in the ctor 786 // before it gets a change to get set, so we need to read it here when we use it) 787 if (mMaxItemId == -1) { 788 mMaxItemId = initializeMaxItemId(db); 789 } 790 791 // Add default hotseat icons 792 loadFavorites(db, new SimpleWorkspaceLoader(this, mContext.getResources(), 793 R.xml.update_workspace)); 794 version = 9; 795 } 796 797 // We bumped the version three time during JB, once to update the launch flags, once to 798 // update the override for the default launch animation and once to set the mimetype 799 // to improve startup performance 800 if (version < 12) { 801 // Contact shortcuts need a different set of flags to be launched now 802 // The updateContactsShortcuts change is idempotent, so we can keep using it like 803 // back in the Donut days 804 updateContactsShortcuts(db); 805 version = 12; 806 } 807 808 if (version < 13) { 809 // With the new shrink-wrapped and re-orderable workspaces, it makes sense 810 // to persist workspace screens and their relative order. 811 mMaxScreenId = 0; 812 813 // This will never happen in the wild, but when we switch to using workspace 814 // screen ids, redo the import from old launcher. 815 sJustLoadedFromOldDb = true; 816 817 addWorkspacesTable(db); 818 version = 13; 819 } 820 821 if (version < 14) { 822 db.beginTransaction(); 823 try { 824 // Insert new column for holding widget provider name 825 db.execSQL("ALTER TABLE favorites " + 826 "ADD COLUMN appWidgetProvider TEXT;"); 827 db.setTransactionSuccessful(); 828 version = 14; 829 } catch (SQLException ex) { 830 // Old version remains, which means we wipe old data 831 Log.e(TAG, ex.getMessage(), ex); 832 } finally { 833 db.endTransaction(); 834 } 835 } 836 837 if (version < 15) { 838 db.beginTransaction(); 839 try { 840 // Insert new column for holding update timestamp 841 db.execSQL("ALTER TABLE favorites " + 842 "ADD COLUMN modified INTEGER NOT NULL DEFAULT 0;"); 843 db.execSQL("ALTER TABLE workspaceScreens " + 844 "ADD COLUMN modified INTEGER NOT NULL DEFAULT 0;"); 845 db.setTransactionSuccessful(); 846 version = 15; 847 } catch (SQLException ex) { 848 // Old version remains, which means we wipe old data 849 Log.e(TAG, ex.getMessage(), ex); 850 } finally { 851 db.endTransaction(); 852 } 853 } 854 855 856 if (version < 16) { 857 db.beginTransaction(); 858 try { 859 // Insert new column for holding restore status 860 db.execSQL("ALTER TABLE favorites " + 861 "ADD COLUMN restored INTEGER NOT NULL DEFAULT 0;"); 862 db.setTransactionSuccessful(); 863 version = 16; 864 } catch (SQLException ex) { 865 // Old version remains, which means we wipe old data 866 Log.e(TAG, ex.getMessage(), ex); 867 } finally { 868 db.endTransaction(); 869 } 870 } 871 872 if (version < 17) { 873 // We use the db version upgrade here to identify users who may not have seen 874 // clings yet (because they weren't available), but for whom the clings are now 875 // available (tablet users). Because one of the possible cling flows (migration) 876 // is very destructive (wipes out workspaces), we want to prevent this from showing 877 // until clear data. We do so by marking that the clings have been shown. 878 LauncherClings.synchonouslyMarkFirstRunClingDismissed(mContext); 879 version = 17; 880 } 881 882 if (version < 18) { 883 // No-op 884 version = 18; 885 } 886 887 if (version < 19) { 888 // Due to a data loss bug, some users may have items associated with screen ids 889 // which no longer exist. Since this can cause other problems, and since the user 890 // will never see these items anyway, we use database upgrade as an opportunity to 891 // clean things up. 892 removeOrphanedItems(db); 893 version = 19; 894 } 895 896 if (version < 20) { 897 // Add userId column 898 if (addProfileColumn(db)) { 899 version = 20; 900 } 901 // else old version remains, which means we wipe old data 902 } 903 904 if (version != DATABASE_VERSION) { 905 Log.w(TAG, "Destroying all old data."); 906 db.execSQL("DROP TABLE IF EXISTS " + TABLE_FAVORITES); 907 db.execSQL("DROP TABLE IF EXISTS " + TABLE_WORKSPACE_SCREENS); 908 909 onCreate(db); 910 } 911 } 912 913 @Override onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion)914 public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) { 915 // This shouldn't happen -- throw our hands up in the air and start over. 916 Log.w(TAG, "Database version downgrade from: " + oldVersion + " to " + newVersion + 917 ". Wiping databse."); 918 createEmptyDB(db); 919 } 920 921 922 /** 923 * Clears all the data for a fresh start. 924 */ createEmptyDB(SQLiteDatabase db)925 public void createEmptyDB(SQLiteDatabase db) { 926 db.execSQL("DROP TABLE IF EXISTS " + TABLE_FAVORITES); 927 db.execSQL("DROP TABLE IF EXISTS " + TABLE_WORKSPACE_SCREENS); 928 onCreate(db); 929 } 930 addProfileColumn(SQLiteDatabase db)931 private boolean addProfileColumn(SQLiteDatabase db) { 932 db.beginTransaction(); 933 try { 934 UserManagerCompat userManager = UserManagerCompat.getInstance(mContext); 935 // Default to the serial number of this user, for older 936 // shortcuts. 937 long userSerialNumber = userManager.getSerialNumberForUser( 938 UserHandleCompat.myUserHandle()); 939 // Insert new column for holding user serial number 940 db.execSQL("ALTER TABLE favorites " + 941 "ADD COLUMN profileId INTEGER DEFAULT " 942 + userSerialNumber + ";"); 943 db.setTransactionSuccessful(); 944 } catch (SQLException ex) { 945 // Old version remains, which means we wipe old data 946 Log.e(TAG, ex.getMessage(), ex); 947 return false; 948 } finally { 949 db.endTransaction(); 950 } 951 return true; 952 } 953 updateContactsShortcuts(SQLiteDatabase db)954 private boolean updateContactsShortcuts(SQLiteDatabase db) { 955 final String selectWhere = buildOrWhereString(Favorites.ITEM_TYPE, 956 new int[] { Favorites.ITEM_TYPE_SHORTCUT }); 957 958 Cursor c = null; 959 final String actionQuickContact = "com.android.contacts.action.QUICK_CONTACT"; 960 db.beginTransaction(); 961 try { 962 // Select and iterate through each matching widget 963 c = db.query(TABLE_FAVORITES, 964 new String[] { Favorites._ID, Favorites.INTENT }, 965 selectWhere, null, null, null, null); 966 if (c == null) return false; 967 968 if (LOGD) Log.d(TAG, "found upgrade cursor count=" + c.getCount()); 969 970 final int idIndex = c.getColumnIndex(Favorites._ID); 971 final int intentIndex = c.getColumnIndex(Favorites.INTENT); 972 973 while (c.moveToNext()) { 974 long favoriteId = c.getLong(idIndex); 975 final String intentUri = c.getString(intentIndex); 976 if (intentUri != null) { 977 try { 978 final Intent intent = Intent.parseUri(intentUri, 0); 979 android.util.Log.d("Home", intent.toString()); 980 final Uri uri = intent.getData(); 981 if (uri != null) { 982 final String data = uri.toString(); 983 if ((Intent.ACTION_VIEW.equals(intent.getAction()) || 984 actionQuickContact.equals(intent.getAction())) && 985 (data.startsWith("content://contacts/people/") || 986 data.startsWith("content://com.android.contacts/" + 987 "contacts/lookup/"))) { 988 989 final Intent newIntent = new Intent(actionQuickContact); 990 // When starting from the launcher, start in a new, cleared task 991 // CLEAR_WHEN_TASK_RESET cannot reset the root of a task, so we 992 // clear the whole thing preemptively here since 993 // QuickContactActivity will finish itself when launching other 994 // detail activities. 995 newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | 996 Intent.FLAG_ACTIVITY_CLEAR_TASK); 997 newIntent.putExtra( 998 Launcher.INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION, true); 999 newIntent.setData(uri); 1000 // Determine the type and also put that in the shortcut 1001 // (that can speed up launch a bit) 1002 newIntent.setDataAndType(uri, newIntent.resolveType(mContext)); 1003 1004 final ContentValues values = new ContentValues(); 1005 values.put(LauncherSettings.Favorites.INTENT, 1006 newIntent.toUri(0)); 1007 1008 String updateWhere = Favorites._ID + "=" + favoriteId; 1009 db.update(TABLE_FAVORITES, values, updateWhere, null); 1010 } 1011 } 1012 } catch (RuntimeException ex) { 1013 Log.e(TAG, "Problem upgrading shortcut", ex); 1014 } catch (URISyntaxException e) { 1015 Log.e(TAG, "Problem upgrading shortcut", e); 1016 } 1017 } 1018 } 1019 1020 db.setTransactionSuccessful(); 1021 } catch (SQLException ex) { 1022 Log.w(TAG, "Problem while upgrading contacts", ex); 1023 return false; 1024 } finally { 1025 db.endTransaction(); 1026 if (c != null) { 1027 c.close(); 1028 } 1029 } 1030 1031 return true; 1032 } 1033 normalizeIcons(SQLiteDatabase db)1034 private void normalizeIcons(SQLiteDatabase db) { 1035 Log.d(TAG, "normalizing icons"); 1036 1037 db.beginTransaction(); 1038 Cursor c = null; 1039 SQLiteStatement update = null; 1040 try { 1041 boolean logged = false; 1042 update = db.compileStatement("UPDATE favorites " 1043 + "SET icon=? WHERE _id=?"); 1044 1045 c = db.rawQuery("SELECT _id, icon FROM favorites WHERE iconType=" + 1046 Favorites.ICON_TYPE_BITMAP, null); 1047 1048 final int idIndex = c.getColumnIndexOrThrow(Favorites._ID); 1049 final int iconIndex = c.getColumnIndexOrThrow(Favorites.ICON); 1050 1051 while (c.moveToNext()) { 1052 long id = c.getLong(idIndex); 1053 byte[] data = c.getBlob(iconIndex); 1054 try { 1055 Bitmap bitmap = Utilities.resampleIconBitmap( 1056 BitmapFactory.decodeByteArray(data, 0, data.length), 1057 mContext); 1058 if (bitmap != null) { 1059 update.bindLong(1, id); 1060 data = ItemInfo.flattenBitmap(bitmap); 1061 if (data != null) { 1062 update.bindBlob(2, data); 1063 update.execute(); 1064 } 1065 bitmap.recycle(); 1066 } 1067 } catch (Exception e) { 1068 if (!logged) { 1069 Log.e(TAG, "Failed normalizing icon " + id, e); 1070 } else { 1071 Log.e(TAG, "Also failed normalizing icon " + id); 1072 } 1073 logged = true; 1074 } 1075 } 1076 db.setTransactionSuccessful(); 1077 } catch (SQLException ex) { 1078 Log.w(TAG, "Problem while allocating appWidgetIds for existing widgets", ex); 1079 } finally { 1080 db.endTransaction(); 1081 if (update != null) { 1082 update.close(); 1083 } 1084 if (c != null) { 1085 c.close(); 1086 } 1087 } 1088 } 1089 1090 // Generates a new ID to use for an object in your database. This method should be only 1091 // called from the main UI thread. As an exception, we do call it when we call the 1092 // constructor from the worker thread; however, this doesn't extend until after the 1093 // constructor is called, and we only pass a reference to LauncherProvider to LauncherApp 1094 // after that point 1095 @Override generateNewItemId()1096 public long generateNewItemId() { 1097 if (mMaxItemId < 0) { 1098 throw new RuntimeException("Error: max item id was not initialized"); 1099 } 1100 mMaxItemId += 1; 1101 return mMaxItemId; 1102 } 1103 1104 @Override insertAndCheck(SQLiteDatabase db, ContentValues values)1105 public long insertAndCheck(SQLiteDatabase db, ContentValues values) { 1106 return dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values); 1107 } 1108 updateMaxItemId(long id)1109 public void updateMaxItemId(long id) { 1110 mMaxItemId = id + 1; 1111 } 1112 checkId(String table, ContentValues values)1113 public void checkId(String table, ContentValues values) { 1114 long id = values.getAsLong(LauncherSettings.BaseLauncherColumns._ID); 1115 if (table == LauncherProvider.TABLE_WORKSPACE_SCREENS) { 1116 mMaxScreenId = Math.max(id, mMaxScreenId); 1117 } else { 1118 mMaxItemId = Math.max(id, mMaxItemId); 1119 } 1120 } 1121 initializeMaxItemId(SQLiteDatabase db)1122 private long initializeMaxItemId(SQLiteDatabase db) { 1123 Cursor c = db.rawQuery("SELECT MAX(_id) FROM favorites", null); 1124 1125 // get the result 1126 final int maxIdIndex = 0; 1127 long id = -1; 1128 if (c != null && c.moveToNext()) { 1129 id = c.getLong(maxIdIndex); 1130 } 1131 if (c != null) { 1132 c.close(); 1133 } 1134 1135 if (id == -1) { 1136 throw new RuntimeException("Error: could not query max item id"); 1137 } 1138 1139 return id; 1140 } 1141 1142 // Generates a new ID to use for an workspace screen in your database. This method 1143 // should be only called from the main UI thread. As an exception, we do call it when we 1144 // call the constructor from the worker thread; however, this doesn't extend until after the 1145 // constructor is called, and we only pass a reference to LauncherProvider to LauncherApp 1146 // after that point generateNewScreenId()1147 public long generateNewScreenId() { 1148 if (mMaxScreenId < 0) { 1149 throw new RuntimeException("Error: max screen id was not initialized"); 1150 } 1151 mMaxScreenId += 1; 1152 // Log to disk 1153 Launcher.addDumpLog(TAG, "11683562 - generateNewScreenId(): " + mMaxScreenId, true); 1154 return mMaxScreenId; 1155 } 1156 updateMaxScreenId(long maxScreenId)1157 public void updateMaxScreenId(long maxScreenId) { 1158 // Log to disk 1159 Launcher.addDumpLog(TAG, "11683562 - updateMaxScreenId(): " + maxScreenId, true); 1160 mMaxScreenId = maxScreenId; 1161 } 1162 initializeMaxScreenId(SQLiteDatabase db)1163 private long initializeMaxScreenId(SQLiteDatabase db) { 1164 Cursor c = db.rawQuery("SELECT MAX(" + LauncherSettings.WorkspaceScreens._ID + ") FROM " + TABLE_WORKSPACE_SCREENS, null); 1165 1166 // get the result 1167 final int maxIdIndex = 0; 1168 long id = -1; 1169 if (c != null && c.moveToNext()) { 1170 id = c.getLong(maxIdIndex); 1171 } 1172 if (c != null) { 1173 c.close(); 1174 } 1175 1176 if (id == -1) { 1177 throw new RuntimeException("Error: could not query max screen id"); 1178 } 1179 1180 // Log to disk 1181 Launcher.addDumpLog(TAG, "11683562 - initializeMaxScreenId(): " + id, true); 1182 return id; 1183 } 1184 1185 /** 1186 * Upgrade existing clock and photo frame widgets into their new widget 1187 * equivalents. 1188 */ convertWidgets(SQLiteDatabase db)1189 private void convertWidgets(SQLiteDatabase db) { 1190 final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext); 1191 final int[] bindSources = new int[] { 1192 Favorites.ITEM_TYPE_WIDGET_CLOCK, 1193 Favorites.ITEM_TYPE_WIDGET_PHOTO_FRAME, 1194 Favorites.ITEM_TYPE_WIDGET_SEARCH, 1195 }; 1196 1197 final String selectWhere = buildOrWhereString(Favorites.ITEM_TYPE, bindSources); 1198 1199 Cursor c = null; 1200 1201 db.beginTransaction(); 1202 try { 1203 // Select and iterate through each matching widget 1204 c = db.query(TABLE_FAVORITES, new String[] { Favorites._ID, Favorites.ITEM_TYPE }, 1205 selectWhere, null, null, null, null); 1206 1207 if (LOGD) Log.d(TAG, "found upgrade cursor count=" + c.getCount()); 1208 1209 final ContentValues values = new ContentValues(); 1210 while (c != null && c.moveToNext()) { 1211 long favoriteId = c.getLong(0); 1212 int favoriteType = c.getInt(1); 1213 1214 // Allocate and update database with new appWidgetId 1215 try { 1216 int appWidgetId = mAppWidgetHost.allocateAppWidgetId(); 1217 1218 if (LOGD) { 1219 Log.d(TAG, "allocated appWidgetId=" + appWidgetId 1220 + " for favoriteId=" + favoriteId); 1221 } 1222 values.clear(); 1223 values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_APPWIDGET); 1224 values.put(Favorites.APPWIDGET_ID, appWidgetId); 1225 1226 // Original widgets might not have valid spans when upgrading 1227 if (favoriteType == Favorites.ITEM_TYPE_WIDGET_SEARCH) { 1228 values.put(LauncherSettings.Favorites.SPANX, 4); 1229 values.put(LauncherSettings.Favorites.SPANY, 1); 1230 } else { 1231 values.put(LauncherSettings.Favorites.SPANX, 2); 1232 values.put(LauncherSettings.Favorites.SPANY, 2); 1233 } 1234 1235 String updateWhere = Favorites._ID + "=" + favoriteId; 1236 db.update(TABLE_FAVORITES, values, updateWhere, null); 1237 1238 if (favoriteType == Favorites.ITEM_TYPE_WIDGET_CLOCK) { 1239 // TODO: check return value 1240 appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, 1241 new ComponentName("com.android.alarmclock", 1242 "com.android.alarmclock.AnalogAppWidgetProvider")); 1243 } else if (favoriteType == Favorites.ITEM_TYPE_WIDGET_PHOTO_FRAME) { 1244 // TODO: check return value 1245 appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, 1246 new ComponentName("com.android.camera", 1247 "com.android.camera.PhotoAppWidgetProvider")); 1248 } else if (favoriteType == Favorites.ITEM_TYPE_WIDGET_SEARCH) { 1249 // TODO: check return value 1250 appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, 1251 getSearchWidgetProvider()); 1252 } 1253 } catch (RuntimeException ex) { 1254 Log.e(TAG, "Problem allocating appWidgetId", ex); 1255 } 1256 } 1257 1258 db.setTransactionSuccessful(); 1259 } catch (SQLException ex) { 1260 Log.w(TAG, "Problem while allocating appWidgetIds for existing widgets", ex); 1261 } finally { 1262 db.endTransaction(); 1263 if (c != null) { 1264 c.close(); 1265 } 1266 } 1267 1268 // Update max item id 1269 mMaxItemId = initializeMaxItemId(db); 1270 if (LOGD) Log.d(TAG, "mMaxItemId: " + mMaxItemId); 1271 } 1272 initializeExternalAdd(ContentValues values)1273 private boolean initializeExternalAdd(ContentValues values) { 1274 // 1. Ensure that externally added items have a valid item id 1275 long id = generateNewItemId(); 1276 values.put(LauncherSettings.Favorites._ID, id); 1277 1278 // 2. In the case of an app widget, and if no app widget id is specified, we 1279 // attempt allocate and bind the widget. 1280 Integer itemType = values.getAsInteger(LauncherSettings.Favorites.ITEM_TYPE); 1281 if (itemType != null && 1282 itemType.intValue() == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET && 1283 !values.containsKey(LauncherSettings.Favorites.APPWIDGET_ID)) { 1284 1285 final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext); 1286 ComponentName cn = ComponentName.unflattenFromString( 1287 values.getAsString(Favorites.APPWIDGET_PROVIDER)); 1288 1289 if (cn != null) { 1290 try { 1291 int appWidgetId = mAppWidgetHost.allocateAppWidgetId(); 1292 values.put(LauncherSettings.Favorites.APPWIDGET_ID, appWidgetId); 1293 if (!appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId,cn)) { 1294 return false; 1295 } 1296 } catch (RuntimeException e) { 1297 Log.e(TAG, "Failed to initialize external widget", e); 1298 return false; 1299 } 1300 } else { 1301 return false; 1302 } 1303 } 1304 1305 // Add screen id if not present 1306 long screenId = values.getAsLong(LauncherSettings.Favorites.SCREEN); 1307 if (!addScreenIdIfNecessary(screenId)) { 1308 return false; 1309 } 1310 return true; 1311 } 1312 1313 // Returns true of screen id exists, or if successfully added addScreenIdIfNecessary(long screenId)1314 private boolean addScreenIdIfNecessary(long screenId) { 1315 if (!hasScreenId(screenId)) { 1316 int rank = getMaxScreenRank() + 1; 1317 1318 ContentValues v = new ContentValues(); 1319 v.put(LauncherSettings.WorkspaceScreens._ID, screenId); 1320 v.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, rank); 1321 if (dbInsertAndCheck(this, getWritableDatabase(), 1322 TABLE_WORKSPACE_SCREENS, null, v) < 0) { 1323 return false; 1324 } 1325 } 1326 return true; 1327 } 1328 hasScreenId(long screenId)1329 private boolean hasScreenId(long screenId) { 1330 SQLiteDatabase db = getWritableDatabase(); 1331 Cursor c = db.rawQuery("SELECT * FROM " + TABLE_WORKSPACE_SCREENS + " WHERE " 1332 + LauncherSettings.WorkspaceScreens._ID + " = " + screenId, null); 1333 if (c != null) { 1334 int count = c.getCount(); 1335 c.close(); 1336 return count > 0; 1337 } else { 1338 return false; 1339 } 1340 } 1341 getMaxScreenRank()1342 private int getMaxScreenRank() { 1343 SQLiteDatabase db = getWritableDatabase(); 1344 Cursor c = db.rawQuery("SELECT MAX(" + LauncherSettings.WorkspaceScreens.SCREEN_RANK 1345 + ") FROM " + TABLE_WORKSPACE_SCREENS, null); 1346 1347 // get the result 1348 final int maxRankIndex = 0; 1349 int rank = -1; 1350 if (c != null && c.moveToNext()) { 1351 rank = c.getInt(maxRankIndex); 1352 } 1353 if (c != null) { 1354 c.close(); 1355 } 1356 1357 return rank; 1358 } 1359 beginDocument(XmlPullParser parser, String firstElementName)1360 private static final void beginDocument(XmlPullParser parser, String firstElementName) 1361 throws XmlPullParserException, IOException { 1362 int type; 1363 while ((type = parser.next()) != XmlPullParser.START_TAG 1364 && type != XmlPullParser.END_DOCUMENT) { 1365 ; 1366 } 1367 1368 if (type != XmlPullParser.START_TAG) { 1369 throw new XmlPullParserException("No start tag found"); 1370 } 1371 1372 if (!parser.getName().equals(firstElementName)) { 1373 throw new XmlPullParserException("Unexpected start tag: found " + parser.getName() + 1374 ", expected " + firstElementName); 1375 } 1376 } 1377 buildMainIntent()1378 private static Intent buildMainIntent() { 1379 Intent intent = new Intent(Intent.ACTION_MAIN, null); 1380 intent.addCategory(Intent.CATEGORY_LAUNCHER); 1381 return intent; 1382 } 1383 loadFavorites(SQLiteDatabase db, WorkspaceLoader loader)1384 private int loadFavorites(SQLiteDatabase db, WorkspaceLoader loader) { 1385 ArrayList<Long> screenIds = new ArrayList<Long>(); 1386 // TODO: Use multiple loaders with fall-back and transaction. 1387 int count = loader.loadLayout(db, screenIds); 1388 1389 // Add the screens specified by the items above 1390 Collections.sort(screenIds); 1391 int rank = 0; 1392 ContentValues values = new ContentValues(); 1393 for (Long id : screenIds) { 1394 values.clear(); 1395 values.put(LauncherSettings.WorkspaceScreens._ID, id); 1396 values.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, rank); 1397 if (dbInsertAndCheck(this, db, TABLE_WORKSPACE_SCREENS, null, values) < 0) { 1398 throw new RuntimeException("Failed initialize screen table" 1399 + "from default layout"); 1400 } 1401 rank++; 1402 } 1403 1404 // Ensure that the max ids are initialized 1405 mMaxItemId = initializeMaxItemId(db); 1406 mMaxScreenId = initializeMaxScreenId(db); 1407 1408 return count; 1409 } 1410 1411 /** 1412 * Loads the default set of favorite packages from an xml file. 1413 * 1414 * @param db The database to write the values into 1415 * @param filterContainerId The specific container id of items to load 1416 * @param the set of screenIds which are used by the favorites 1417 */ loadFavoritesRecursive(SQLiteDatabase db, Resources res, int workspaceResourceId, ArrayList<Long> screenIds)1418 private int loadFavoritesRecursive(SQLiteDatabase db, Resources res, int workspaceResourceId, 1419 ArrayList<Long> screenIds) { 1420 1421 ContentValues values = new ContentValues(); 1422 if (LOGD) Log.v(TAG, String.format("Loading favorites from resid=0x%08x", workspaceResourceId)); 1423 1424 int count = 0; 1425 try { 1426 XmlResourceParser parser = res.getXml(workspaceResourceId); 1427 beginDocument(parser, TAG_FAVORITES); 1428 1429 final int depth = parser.getDepth(); 1430 1431 int type; 1432 while (((type = parser.next()) != XmlPullParser.END_TAG || 1433 parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) { 1434 1435 if (type != XmlPullParser.START_TAG) { 1436 continue; 1437 } 1438 1439 boolean added = false; 1440 final String name = parser.getName(); 1441 1442 if (TAG_INCLUDE.equals(name)) { 1443 1444 final int resId = getAttributeResourceValue(parser, ATTR_WORKSPACE, 0); 1445 1446 if (LOGD) Log.v(TAG, String.format(("%" + (2*(depth+1)) + "s<include workspace=%08x>"), 1447 "", resId)); 1448 1449 if (resId != 0 && resId != workspaceResourceId) { 1450 // recursively load some more favorites, why not? 1451 count += loadFavoritesRecursive(db, res, resId, screenIds); 1452 added = false; 1453 } else { 1454 Log.w(TAG, String.format("Skipping <include workspace=0x%08x>", resId)); 1455 } 1456 1457 if (LOGD) Log.v(TAG, String.format(("%" + (2*(depth+1)) + "s</include>"), "")); 1458 continue; 1459 } 1460 1461 // Assuming it's a <favorite> at this point 1462 long container = LauncherSettings.Favorites.CONTAINER_DESKTOP; 1463 String strContainer = getAttributeValue(parser, ATTR_CONTAINER); 1464 if (strContainer != null) { 1465 container = Long.valueOf(strContainer); 1466 } 1467 1468 String screen = getAttributeValue(parser, ATTR_SCREEN); 1469 String x = getAttributeValue(parser, ATTR_X); 1470 String y = getAttributeValue(parser, ATTR_Y); 1471 1472 values.clear(); 1473 values.put(LauncherSettings.Favorites.CONTAINER, container); 1474 values.put(LauncherSettings.Favorites.SCREEN, screen); 1475 values.put(LauncherSettings.Favorites.CELLX, x); 1476 values.put(LauncherSettings.Favorites.CELLY, y); 1477 1478 if (LOGD) { 1479 final String title = getAttributeValue(parser, ATTR_TITLE); 1480 final String pkg = getAttributeValue(parser, ATTR_PACKAGE_NAME); 1481 final String something = title != null ? title : pkg; 1482 Log.v(TAG, String.format( 1483 ("%" + (2*(depth+1)) + "s<%s%s c=%d s=%s x=%s y=%s>"), 1484 "", name, 1485 (something == null ? "" : (" \"" + something + "\"")), 1486 container, screen, x, y)); 1487 } 1488 1489 if (TAG_FAVORITE.equals(name)) { 1490 long id = addAppShortcut(db, values, parser); 1491 added = id >= 0; 1492 } else if (TAG_APPWIDGET.equals(name)) { 1493 added = addAppWidget(parser, type, db, values); 1494 } else if (TAG_SHORTCUT.equals(name)) { 1495 long id = addUriShortcut(db, values, res, parser); 1496 added = id >= 0; 1497 } else if (TAG_RESOLVE.equals(name)) { 1498 // This looks through the contained favorites (or meta-favorites) and 1499 // attempts to add them as shortcuts in the fallback group's location 1500 // until one is added successfully. 1501 added = false; 1502 final int groupDepth = parser.getDepth(); 1503 while ((type = parser.next()) != XmlPullParser.END_TAG || 1504 parser.getDepth() > groupDepth) { 1505 if (type != XmlPullParser.START_TAG) { 1506 continue; 1507 } 1508 final String fallback_item_name = parser.getName(); 1509 if (!added) { 1510 if (TAG_FAVORITE.equals(fallback_item_name)) { 1511 final long id = addAppShortcut(db, values, parser); 1512 added = id >= 0; 1513 } else { 1514 Log.e(TAG, "Fallback groups can contain only favorites, found " 1515 + fallback_item_name); 1516 } 1517 } 1518 } 1519 } else if (TAG_FOLDER.equals(name)) { 1520 // Folder contents are nested in this XML file 1521 added = loadFolder(db, values, res, parser); 1522 1523 } else if (TAG_PARTNER_FOLDER.equals(name)) { 1524 // Folder contents come from an external XML resource 1525 final Partner partner = Partner.get(mPackageManager); 1526 if (partner != null) { 1527 final Resources partnerRes = partner.getResources(); 1528 final int resId = partnerRes.getIdentifier(Partner.RES_FOLDER, 1529 "xml", partner.getPackageName()); 1530 if (resId != 0) { 1531 final XmlResourceParser partnerParser = partnerRes.getXml(resId); 1532 beginDocument(partnerParser, TAG_FOLDER); 1533 added = loadFolder(db, values, partnerRes, partnerParser); 1534 } 1535 } 1536 } 1537 if (added) { 1538 long screenId = Long.parseLong(screen); 1539 // Keep track of the set of screens which need to be added to the db. 1540 if (!screenIds.contains(screenId) && 1541 container == LauncherSettings.Favorites.CONTAINER_DESKTOP) { 1542 screenIds.add(screenId); 1543 } 1544 count++; 1545 } 1546 } 1547 } catch (XmlPullParserException e) { 1548 Log.w(TAG, "Got exception parsing favorites.", e); 1549 } catch (IOException e) { 1550 Log.w(TAG, "Got exception parsing favorites.", e); 1551 } catch (RuntimeException e) { 1552 Log.w(TAG, "Got exception parsing favorites.", e); 1553 } 1554 return count; 1555 } 1556 1557 /** 1558 * Parse folder items starting at {@link XmlPullParser} location. Allow recursive 1559 * includes of items. 1560 */ addToFolder(SQLiteDatabase db, Resources res, XmlResourceParser parser, ArrayList<Long> folderItems, long folderId)1561 private void addToFolder(SQLiteDatabase db, Resources res, XmlResourceParser parser, 1562 ArrayList<Long> folderItems, long folderId) throws IOException, XmlPullParserException { 1563 int type; 1564 int folderDepth = parser.getDepth(); 1565 while ((type = parser.next()) != XmlPullParser.END_TAG || 1566 parser.getDepth() > folderDepth) { 1567 if (type != XmlPullParser.START_TAG) { 1568 continue; 1569 } 1570 final String tag = parser.getName(); 1571 1572 final ContentValues childValues = new ContentValues(); 1573 childValues.put(LauncherSettings.Favorites.CONTAINER, folderId); 1574 1575 if (LOGD) { 1576 final String pkg = getAttributeValue(parser, ATTR_PACKAGE_NAME); 1577 final String uri = getAttributeValue(parser, ATTR_URI); 1578 Log.v(TAG, String.format(("%" + (2*(folderDepth+1)) + "s<%s \"%s\">"), "", 1579 tag, uri != null ? uri : pkg)); 1580 } 1581 1582 if (TAG_FAVORITE.equals(tag) && folderId >= 0) { 1583 final long id = addAppShortcut(db, childValues, parser); 1584 if (id >= 0) { 1585 folderItems.add(id); 1586 } 1587 } else if (TAG_SHORTCUT.equals(tag) && folderId >= 0) { 1588 final long id = addUriShortcut(db, childValues, res, parser); 1589 if (id >= 0) { 1590 folderItems.add(id); 1591 } 1592 } else if (TAG_INCLUDE.equals(tag) && folderId >= 0) { 1593 addToFolder(db, res, parser, folderItems, folderId); 1594 } else { 1595 throw new RuntimeException("Folders can contain only shortcuts"); 1596 } 1597 } 1598 } 1599 1600 /** 1601 * Parse folder starting at current {@link XmlPullParser} location. 1602 */ loadFolder(SQLiteDatabase db, ContentValues values, Resources res, XmlResourceParser parser)1603 private boolean loadFolder(SQLiteDatabase db, ContentValues values, Resources res, 1604 XmlResourceParser parser) throws IOException, XmlPullParserException { 1605 final String title; 1606 final int titleResId = getAttributeResourceValue(parser, ATTR_TITLE, 0); 1607 if (titleResId != 0) { 1608 title = res.getString(titleResId); 1609 } else { 1610 title = mContext.getResources().getString(R.string.folder_name); 1611 } 1612 1613 values.put(LauncherSettings.Favorites.TITLE, title); 1614 long folderId = addFolder(db, values); 1615 boolean added = folderId >= 0; 1616 1617 ArrayList<Long> folderItems = new ArrayList<Long>(); 1618 addToFolder(db, res, parser, folderItems, folderId); 1619 1620 // We can only have folders with >= 2 items, so we need to remove the 1621 // folder and clean up if less than 2 items were included, or some 1622 // failed to add, and less than 2 were actually added 1623 if (folderItems.size() < 2 && folderId >= 0) { 1624 // Delete the folder 1625 deleteId(db, folderId); 1626 1627 // If we have a single item, promote it to where the folder 1628 // would have been. 1629 if (folderItems.size() == 1) { 1630 final ContentValues childValues = new ContentValues(); 1631 copyInteger(values, childValues, LauncherSettings.Favorites.CONTAINER); 1632 copyInteger(values, childValues, LauncherSettings.Favorites.SCREEN); 1633 copyInteger(values, childValues, LauncherSettings.Favorites.CELLX); 1634 copyInteger(values, childValues, LauncherSettings.Favorites.CELLY); 1635 1636 final long id = folderItems.get(0); 1637 db.update(TABLE_FAVORITES, childValues, 1638 LauncherSettings.Favorites._ID + "=" + id, null); 1639 } else { 1640 added = false; 1641 } 1642 } 1643 return added; 1644 } 1645 1646 // A meta shortcut attempts to resolve an intent specified as a URI in the XML, if a 1647 // logical choice for what shortcut should be used for that intent exists, then it is 1648 // added. Otherwise add nothing. addAppShortcutByUri(SQLiteDatabase db, ContentValues values, String intentUri)1649 private long addAppShortcutByUri(SQLiteDatabase db, ContentValues values, 1650 String intentUri) { 1651 Intent metaIntent; 1652 try { 1653 metaIntent = Intent.parseUri(intentUri, 0); 1654 } catch (URISyntaxException e) { 1655 Log.e(TAG, "Unable to add meta-favorite: " + intentUri, e); 1656 return -1; 1657 } 1658 1659 ResolveInfo resolved = mPackageManager.resolveActivity(metaIntent, 1660 PackageManager.MATCH_DEFAULT_ONLY); 1661 final List<ResolveInfo> appList = mPackageManager.queryIntentActivities( 1662 metaIntent, PackageManager.MATCH_DEFAULT_ONLY); 1663 1664 // Verify that the result is an app and not just the resolver dialog asking which 1665 // app to use. 1666 if (wouldLaunchResolverActivity(resolved, appList)) { 1667 // If only one of the results is a system app then choose that as the default. 1668 final ResolveInfo systemApp = getSingleSystemActivity(appList); 1669 if (systemApp == null) { 1670 // There is no logical choice for this meta-favorite, so rather than making 1671 // a bad choice just add nothing. 1672 Log.w(TAG, "No preference or single system activity found for " 1673 + metaIntent.toString()); 1674 return -1; 1675 } 1676 resolved = systemApp; 1677 } 1678 final ActivityInfo info = resolved.activityInfo; 1679 final Intent intent = mPackageManager.getLaunchIntentForPackage(info.packageName); 1680 if (intent == null) { 1681 return -1; 1682 } 1683 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | 1684 Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); 1685 1686 return addAppShortcut(db, values, info.loadLabel(mPackageManager).toString(), intent); 1687 } 1688 getSingleSystemActivity(List<ResolveInfo> appList)1689 private ResolveInfo getSingleSystemActivity(List<ResolveInfo> appList) { 1690 ResolveInfo systemResolve = null; 1691 final int N = appList.size(); 1692 for (int i = 0; i < N; ++i) { 1693 try { 1694 ApplicationInfo info = mPackageManager.getApplicationInfo( 1695 appList.get(i).activityInfo.packageName, 0); 1696 if ((info.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { 1697 if (systemResolve != null) { 1698 return null; 1699 } else { 1700 systemResolve = appList.get(i); 1701 } 1702 } 1703 } catch (PackageManager.NameNotFoundException e) { 1704 Log.w(TAG, "Unable to get info about resolve results", e); 1705 return null; 1706 } 1707 } 1708 return systemResolve; 1709 } 1710 wouldLaunchResolverActivity(ResolveInfo resolved, List<ResolveInfo> appList)1711 private boolean wouldLaunchResolverActivity(ResolveInfo resolved, 1712 List<ResolveInfo> appList) { 1713 // If the list contains the above resolved activity, then it can't be 1714 // ResolverActivity itself. 1715 for (int i = 0; i < appList.size(); ++i) { 1716 ResolveInfo tmp = appList.get(i); 1717 if (tmp.activityInfo.name.equals(resolved.activityInfo.name) 1718 && tmp.activityInfo.packageName.equals(resolved.activityInfo.packageName)) { 1719 return false; 1720 } 1721 } 1722 return true; 1723 } 1724 addAppShortcut(SQLiteDatabase db, ContentValues values, XmlResourceParser parser)1725 private long addAppShortcut(SQLiteDatabase db, ContentValues values, 1726 XmlResourceParser parser) { 1727 final String packageName = getAttributeValue(parser, ATTR_PACKAGE_NAME); 1728 final String className = getAttributeValue(parser, ATTR_CLASS_NAME); 1729 final String uri = getAttributeValue(parser, ATTR_URI); 1730 1731 if (!TextUtils.isEmpty(packageName) && !TextUtils.isEmpty(className)) { 1732 ActivityInfo info; 1733 try { 1734 ComponentName cn; 1735 try { 1736 cn = new ComponentName(packageName, className); 1737 info = mPackageManager.getActivityInfo(cn, 0); 1738 } catch (PackageManager.NameNotFoundException nnfe) { 1739 String[] packages = mPackageManager.currentToCanonicalPackageNames( 1740 new String[] { packageName }); 1741 cn = new ComponentName(packages[0], className); 1742 info = mPackageManager.getActivityInfo(cn, 0); 1743 } 1744 final Intent intent = buildMainIntent(); 1745 intent.setComponent(cn); 1746 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | 1747 Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); 1748 1749 return addAppShortcut(db, values, info.loadLabel(mPackageManager).toString(), 1750 intent); 1751 } catch (PackageManager.NameNotFoundException e) { 1752 Log.w(TAG, "Unable to add favorite: " + packageName + 1753 "/" + className, e); 1754 } 1755 return -1; 1756 } else if (!TextUtils.isEmpty(uri)) { 1757 // If no component specified try to find a shortcut to add from the URI. 1758 return addAppShortcutByUri(db, values, uri); 1759 } else { 1760 Log.e(TAG, "Skipping invalid <favorite> with no component or uri"); 1761 return -1; 1762 } 1763 } 1764 addAppShortcut(SQLiteDatabase db, ContentValues values, String title, Intent intent)1765 private long addAppShortcut(SQLiteDatabase db, ContentValues values, String title, 1766 Intent intent) { 1767 long id = generateNewItemId(); 1768 values.put(Favorites.INTENT, intent.toUri(0)); 1769 values.put(Favorites.TITLE, title); 1770 values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_APPLICATION); 1771 values.put(Favorites.SPANX, 1); 1772 values.put(Favorites.SPANY, 1); 1773 values.put(Favorites._ID, id); 1774 if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values) < 0) { 1775 return -1; 1776 } else { 1777 return id; 1778 } 1779 } 1780 addFolder(SQLiteDatabase db, ContentValues values)1781 private long addFolder(SQLiteDatabase db, ContentValues values) { 1782 values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_FOLDER); 1783 values.put(Favorites.SPANX, 1); 1784 values.put(Favorites.SPANY, 1); 1785 long id = generateNewItemId(); 1786 values.put(Favorites._ID, id); 1787 if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values) <= 0) { 1788 return -1; 1789 } else { 1790 return id; 1791 } 1792 } 1793 getSearchWidgetProvider()1794 private ComponentName getSearchWidgetProvider() { 1795 SearchManager searchManager = 1796 (SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE); 1797 ComponentName searchComponent = searchManager.getGlobalSearchActivity(); 1798 if (searchComponent == null) return null; 1799 return getProviderInPackage(searchComponent.getPackageName()); 1800 } 1801 1802 /** 1803 * Gets an appwidget provider from the given package. If the package contains more than 1804 * one appwidget provider, an arbitrary one is returned. 1805 */ getProviderInPackage(String packageName)1806 private ComponentName getProviderInPackage(String packageName) { 1807 AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext); 1808 List<AppWidgetProviderInfo> providers = appWidgetManager.getInstalledProviders(); 1809 if (providers == null) return null; 1810 final int providerCount = providers.size(); 1811 for (int i = 0; i < providerCount; i++) { 1812 ComponentName provider = providers.get(i).provider; 1813 if (provider != null && provider.getPackageName().equals(packageName)) { 1814 return provider; 1815 } 1816 } 1817 return null; 1818 } 1819 addAppWidget(XmlResourceParser parser, int type, SQLiteDatabase db, ContentValues values)1820 private boolean addAppWidget(XmlResourceParser parser, int type, 1821 SQLiteDatabase db, ContentValues values) 1822 throws XmlPullParserException, IOException { 1823 1824 String packageName = getAttributeValue(parser, ATTR_PACKAGE_NAME); 1825 String className = getAttributeValue(parser, ATTR_CLASS_NAME); 1826 1827 if (packageName == null || className == null) { 1828 return false; 1829 } 1830 1831 boolean hasPackage = true; 1832 ComponentName cn = new ComponentName(packageName, className); 1833 try { 1834 mPackageManager.getReceiverInfo(cn, 0); 1835 } catch (Exception e) { 1836 String[] packages = mPackageManager.currentToCanonicalPackageNames( 1837 new String[] { packageName }); 1838 cn = new ComponentName(packages[0], className); 1839 try { 1840 mPackageManager.getReceiverInfo(cn, 0); 1841 } catch (Exception e1) { 1842 System.out.println("Can't find widget provider: " + className); 1843 hasPackage = false; 1844 } 1845 } 1846 1847 if (hasPackage) { 1848 String spanX = getAttributeValue(parser, ATTR_SPAN_X); 1849 String spanY = getAttributeValue(parser, ATTR_SPAN_Y); 1850 1851 values.put(Favorites.SPANX, spanX); 1852 values.put(Favorites.SPANY, spanY); 1853 1854 // Read the extras 1855 Bundle extras = new Bundle(); 1856 int widgetDepth = parser.getDepth(); 1857 while ((type = parser.next()) != XmlPullParser.END_TAG || 1858 parser.getDepth() > widgetDepth) { 1859 if (type != XmlPullParser.START_TAG) { 1860 continue; 1861 } 1862 1863 if (TAG_EXTRA.equals(parser.getName())) { 1864 String key = getAttributeValue(parser, ATTR_KEY); 1865 String value = getAttributeValue(parser, ATTR_VALUE); 1866 if (key != null && value != null) { 1867 extras.putString(key, value); 1868 } else { 1869 throw new RuntimeException("Widget extras must have a key and value"); 1870 } 1871 } else { 1872 throw new RuntimeException("Widgets can contain only extras"); 1873 } 1874 } 1875 1876 return addAppWidget(db, values, cn, extras); 1877 } 1878 1879 return false; 1880 } 1881 addAppWidget(SQLiteDatabase db, ContentValues values, ComponentName cn, Bundle extras)1882 private boolean addAppWidget(SQLiteDatabase db, ContentValues values, ComponentName cn, 1883 Bundle extras) { 1884 boolean allocatedAppWidgets = false; 1885 final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext); 1886 1887 try { 1888 int appWidgetId = mAppWidgetHost.allocateAppWidgetId(); 1889 1890 values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_APPWIDGET); 1891 values.put(Favorites.APPWIDGET_ID, appWidgetId); 1892 values.put(Favorites.APPWIDGET_PROVIDER, cn.flattenToString()); 1893 values.put(Favorites._ID, generateNewItemId()); 1894 dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values); 1895 1896 allocatedAppWidgets = true; 1897 1898 // TODO: need to check return value 1899 appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, cn); 1900 1901 // Send a broadcast to configure the widget 1902 if (extras != null && !extras.isEmpty()) { 1903 Intent intent = new Intent(ACTION_APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE); 1904 intent.setComponent(cn); 1905 intent.putExtras(extras); 1906 intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); 1907 mContext.sendBroadcast(intent); 1908 } 1909 } catch (RuntimeException ex) { 1910 Log.e(TAG, "Problem allocating appWidgetId", ex); 1911 } 1912 1913 return allocatedAppWidgets; 1914 } 1915 addUriShortcut(SQLiteDatabase db, ContentValues values, Resources res, XmlResourceParser parser)1916 private long addUriShortcut(SQLiteDatabase db, ContentValues values, Resources res, 1917 XmlResourceParser parser) { 1918 final int iconResId = getAttributeResourceValue(parser, ATTR_ICON, 0); 1919 final int titleResId = getAttributeResourceValue(parser, ATTR_TITLE, 0); 1920 1921 Intent intent; 1922 String uri = null; 1923 try { 1924 uri = getAttributeValue(parser, ATTR_URI); 1925 intent = Intent.parseUri(uri, 0); 1926 } catch (URISyntaxException e) { 1927 Log.w(TAG, "Shortcut has malformed uri: " + uri); 1928 return -1; // Oh well 1929 } 1930 1931 if (iconResId == 0 || titleResId == 0) { 1932 Log.w(TAG, "Shortcut is missing title or icon resource ID"); 1933 return -1; 1934 } 1935 1936 long id = generateNewItemId(); 1937 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 1938 values.put(Favorites.INTENT, intent.toUri(0)); 1939 values.put(Favorites.TITLE, res.getString(titleResId)); 1940 values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_SHORTCUT); 1941 values.put(Favorites.SPANX, 1); 1942 values.put(Favorites.SPANY, 1); 1943 values.put(Favorites.ICON_TYPE, Favorites.ICON_TYPE_RESOURCE); 1944 values.put(Favorites.ICON_PACKAGE, res.getResourcePackageName(iconResId)); 1945 values.put(Favorites.ICON_RESOURCE, res.getResourceName(iconResId)); 1946 values.put(Favorites._ID, id); 1947 1948 if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values) < 0) { 1949 return -1; 1950 } 1951 return id; 1952 } 1953 migrateLauncher2Shortcuts(SQLiteDatabase db, Uri uri)1954 private void migrateLauncher2Shortcuts(SQLiteDatabase db, Uri uri) { 1955 final ContentResolver resolver = mContext.getContentResolver(); 1956 Cursor c = null; 1957 int count = 0; 1958 int curScreen = 0; 1959 1960 try { 1961 c = resolver.query(uri, null, null, null, "title ASC"); 1962 } catch (Exception e) { 1963 // Ignore 1964 } 1965 1966 // We already have a favorites database in the old provider 1967 if (c != null) { 1968 try { 1969 if (c.getCount() > 0) { 1970 final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID); 1971 final int intentIndex 1972 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.INTENT); 1973 final int titleIndex 1974 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE); 1975 final int iconTypeIndex 1976 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_TYPE); 1977 final int iconIndex 1978 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON); 1979 final int iconPackageIndex 1980 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_PACKAGE); 1981 final int iconResourceIndex 1982 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_RESOURCE); 1983 final int containerIndex 1984 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER); 1985 final int itemTypeIndex 1986 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE); 1987 final int screenIndex 1988 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN); 1989 final int cellXIndex 1990 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX); 1991 final int cellYIndex 1992 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY); 1993 final int uriIndex 1994 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.URI); 1995 final int displayModeIndex 1996 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.DISPLAY_MODE); 1997 final int profileIndex 1998 = c.getColumnIndex(LauncherSettings.Favorites.PROFILE_ID); 1999 2000 int i = 0; 2001 int curX = 0; 2002 int curY = 0; 2003 2004 final LauncherAppState app = LauncherAppState.getInstance(); 2005 final DeviceProfile grid = app.getDynamicGrid().getDeviceProfile(); 2006 final int width = (int) grid.numColumns; 2007 final int height = (int) grid.numRows; 2008 final int hotseatWidth = (int) grid.numHotseatIcons; 2009 2010 final HashSet<String> seenIntents = new HashSet<String>(c.getCount()); 2011 2012 final ArrayList<ContentValues> shortcuts = new ArrayList<ContentValues>(); 2013 final ArrayList<ContentValues> folders = new ArrayList<ContentValues>(); 2014 final SparseArray<ContentValues> hotseat = new SparseArray<ContentValues>(); 2015 2016 while (c.moveToNext()) { 2017 final int itemType = c.getInt(itemTypeIndex); 2018 if (itemType != Favorites.ITEM_TYPE_APPLICATION 2019 && itemType != Favorites.ITEM_TYPE_SHORTCUT 2020 && itemType != Favorites.ITEM_TYPE_FOLDER) { 2021 continue; 2022 } 2023 2024 final int cellX = c.getInt(cellXIndex); 2025 final int cellY = c.getInt(cellYIndex); 2026 final int screen = c.getInt(screenIndex); 2027 int container = c.getInt(containerIndex); 2028 final String intentStr = c.getString(intentIndex); 2029 2030 UserManagerCompat userManager = UserManagerCompat.getInstance(mContext); 2031 UserHandleCompat userHandle; 2032 final long userSerialNumber; 2033 if (profileIndex != -1 && !c.isNull(profileIndex)) { 2034 userSerialNumber = c.getInt(profileIndex); 2035 userHandle = userManager.getUserForSerialNumber(userSerialNumber); 2036 } else { 2037 // Default to the serial number of this user, for older 2038 // shortcuts. 2039 userHandle = UserHandleCompat.myUserHandle(); 2040 userSerialNumber = userManager.getSerialNumberForUser(userHandle); 2041 } 2042 Launcher.addDumpLog(TAG, "migrating \"" 2043 + c.getString(titleIndex) + "\" (" 2044 + cellX + "," + cellY + "@" 2045 + LauncherSettings.Favorites.containerToString(container) 2046 + "/" + screen 2047 + "): " + intentStr, true); 2048 2049 if (itemType != Favorites.ITEM_TYPE_FOLDER) { 2050 2051 final Intent intent; 2052 final ComponentName cn; 2053 try { 2054 intent = Intent.parseUri(intentStr, 0); 2055 } catch (URISyntaxException e) { 2056 // bogus intent? 2057 Launcher.addDumpLog(TAG, 2058 "skipping invalid intent uri", true); 2059 continue; 2060 } 2061 2062 cn = intent.getComponent(); 2063 if (TextUtils.isEmpty(intentStr)) { 2064 // no intent? no icon 2065 Launcher.addDumpLog(TAG, "skipping empty intent", true); 2066 continue; 2067 } else if (cn != null && 2068 !LauncherModel.isValidPackageActivity(mContext, cn, 2069 userHandle)) { 2070 // component no longer exists. 2071 Launcher.addDumpLog(TAG, "skipping item whose component " + 2072 "no longer exists.", true); 2073 continue; 2074 } else if (container == 2075 LauncherSettings.Favorites.CONTAINER_DESKTOP) { 2076 // Dedupe icons directly on the workspace 2077 2078 // Canonicalize 2079 // the Play Store sets the package parameter, but Launcher 2080 // does not, so we clear that out to keep them the same 2081 intent.setPackage(null); 2082 final String key = intent.toUri(0); 2083 if (seenIntents.contains(key)) { 2084 Launcher.addDumpLog(TAG, "skipping duplicate", true); 2085 continue; 2086 } else { 2087 seenIntents.add(key); 2088 } 2089 } 2090 } 2091 2092 ContentValues values = new ContentValues(c.getColumnCount()); 2093 values.put(LauncherSettings.Favorites._ID, c.getInt(idIndex)); 2094 values.put(LauncherSettings.Favorites.INTENT, intentStr); 2095 values.put(LauncherSettings.Favorites.TITLE, c.getString(titleIndex)); 2096 values.put(LauncherSettings.Favorites.ICON_TYPE, 2097 c.getInt(iconTypeIndex)); 2098 values.put(LauncherSettings.Favorites.ICON, c.getBlob(iconIndex)); 2099 values.put(LauncherSettings.Favorites.ICON_PACKAGE, 2100 c.getString(iconPackageIndex)); 2101 values.put(LauncherSettings.Favorites.ICON_RESOURCE, 2102 c.getString(iconResourceIndex)); 2103 values.put(LauncherSettings.Favorites.ITEM_TYPE, itemType); 2104 values.put(LauncherSettings.Favorites.APPWIDGET_ID, -1); 2105 values.put(LauncherSettings.Favorites.URI, c.getString(uriIndex)); 2106 values.put(LauncherSettings.Favorites.DISPLAY_MODE, 2107 c.getInt(displayModeIndex)); 2108 values.put(LauncherSettings.Favorites.PROFILE_ID, userSerialNumber); 2109 2110 if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { 2111 hotseat.put(screen, values); 2112 } 2113 2114 if (container != LauncherSettings.Favorites.CONTAINER_DESKTOP) { 2115 // In a folder or in the hotseat, preserve position 2116 values.put(LauncherSettings.Favorites.SCREEN, screen); 2117 values.put(LauncherSettings.Favorites.CELLX, cellX); 2118 values.put(LauncherSettings.Favorites.CELLY, cellY); 2119 } else { 2120 // For items contained directly on one of the workspace screen, 2121 // we'll determine their location (screen, x, y) in a second pass. 2122 } 2123 2124 values.put(LauncherSettings.Favorites.CONTAINER, container); 2125 2126 if (itemType != Favorites.ITEM_TYPE_FOLDER) { 2127 shortcuts.add(values); 2128 } else { 2129 folders.add(values); 2130 } 2131 } 2132 2133 // Now that we have all the hotseat icons, let's go through them left-right 2134 // and assign valid locations for them in the new hotseat 2135 final int N = hotseat.size(); 2136 for (int idx=0; idx<N; idx++) { 2137 int hotseatX = hotseat.keyAt(idx); 2138 ContentValues values = hotseat.valueAt(idx); 2139 2140 if (hotseatX == grid.hotseatAllAppsRank) { 2141 // let's drop this in the next available hole in the hotseat 2142 while (++hotseatX < hotseatWidth) { 2143 if (hotseat.get(hotseatX) == null) { 2144 // found a spot! move it here 2145 values.put(LauncherSettings.Favorites.SCREEN, 2146 hotseatX); 2147 break; 2148 } 2149 } 2150 } 2151 if (hotseatX >= hotseatWidth) { 2152 // no room for you in the hotseat? it's off to the desktop with you 2153 values.put(LauncherSettings.Favorites.CONTAINER, 2154 Favorites.CONTAINER_DESKTOP); 2155 } 2156 } 2157 2158 final ArrayList<ContentValues> allItems = new ArrayList<ContentValues>(); 2159 // Folders first 2160 allItems.addAll(folders); 2161 // Then shortcuts 2162 allItems.addAll(shortcuts); 2163 2164 // Layout all the folders 2165 for (ContentValues values: allItems) { 2166 if (values.getAsInteger(LauncherSettings.Favorites.CONTAINER) != 2167 LauncherSettings.Favorites.CONTAINER_DESKTOP) { 2168 // Hotseat items and folder items have already had their 2169 // location information set. Nothing to be done here. 2170 continue; 2171 } 2172 values.put(LauncherSettings.Favorites.SCREEN, curScreen); 2173 values.put(LauncherSettings.Favorites.CELLX, curX); 2174 values.put(LauncherSettings.Favorites.CELLY, curY); 2175 curX = (curX + 1) % width; 2176 if (curX == 0) { 2177 curY = (curY + 1); 2178 } 2179 // Leave the last row of icons blank on every screen 2180 if (curY == height - 1) { 2181 curScreen = (int) generateNewScreenId(); 2182 curY = 0; 2183 } 2184 } 2185 2186 if (allItems.size() > 0) { 2187 db.beginTransaction(); 2188 try { 2189 for (ContentValues row: allItems) { 2190 if (row == null) continue; 2191 if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, row) 2192 < 0) { 2193 return; 2194 } else { 2195 count++; 2196 } 2197 } 2198 db.setTransactionSuccessful(); 2199 } finally { 2200 db.endTransaction(); 2201 } 2202 } 2203 2204 db.beginTransaction(); 2205 try { 2206 for (i=0; i<=curScreen; i++) { 2207 final ContentValues values = new ContentValues(); 2208 values.put(LauncherSettings.WorkspaceScreens._ID, i); 2209 values.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, i); 2210 if (dbInsertAndCheck(this, db, TABLE_WORKSPACE_SCREENS, null, values) 2211 < 0) { 2212 return; 2213 } 2214 } 2215 db.setTransactionSuccessful(); 2216 } finally { 2217 db.endTransaction(); 2218 } 2219 } 2220 } finally { 2221 c.close(); 2222 } 2223 } 2224 2225 Launcher.addDumpLog(TAG, "migrated " + count + " icons from Launcher2 into " 2226 + (curScreen+1) + " screens", true); 2227 2228 // ensure that new screens are created to hold these icons 2229 setFlagJustLoadedOldDb(); 2230 2231 // Update max IDs; very important since we just grabbed IDs from another database 2232 mMaxItemId = initializeMaxItemId(db); 2233 mMaxScreenId = initializeMaxScreenId(db); 2234 if (LOGD) Log.d(TAG, "mMaxItemId: " + mMaxItemId + " mMaxScreenId: " + mMaxScreenId); 2235 } 2236 } 2237 2238 /** 2239 * Build a query string that will match any row where the column matches 2240 * anything in the values list. 2241 */ buildOrWhereString(String column, int[] values)2242 private static String buildOrWhereString(String column, int[] values) { 2243 StringBuilder selectWhere = new StringBuilder(); 2244 for (int i = values.length - 1; i >= 0; i--) { 2245 selectWhere.append(column).append("=").append(values[i]); 2246 if (i > 0) { 2247 selectWhere.append(" OR "); 2248 } 2249 } 2250 return selectWhere.toString(); 2251 } 2252 2253 /** 2254 * Return attribute value, attempting launcher-specific namespace first 2255 * before falling back to anonymous attribute. 2256 */ getAttributeValue(XmlResourceParser parser, String attribute)2257 private static String getAttributeValue(XmlResourceParser parser, String attribute) { 2258 String value = parser.getAttributeValue( 2259 "http://schemas.android.com/apk/res-auto/com.android.launcher3", attribute); 2260 if (value == null) { 2261 value = parser.getAttributeValue(null, attribute); 2262 } 2263 return value; 2264 } 2265 2266 /** 2267 * Return attribute resource value, attempting launcher-specific namespace 2268 * first before falling back to anonymous attribute. 2269 */ getAttributeResourceValue(XmlResourceParser parser, String attribute, int defaultValue)2270 private static int getAttributeResourceValue(XmlResourceParser parser, String attribute, 2271 int defaultValue) { 2272 int value = parser.getAttributeResourceValue( 2273 "http://schemas.android.com/apk/res-auto/com.android.launcher3", attribute, 2274 defaultValue); 2275 if (value == defaultValue) { 2276 value = parser.getAttributeResourceValue(null, attribute, defaultValue); 2277 } 2278 return value; 2279 } 2280 copyInteger(ContentValues from, ContentValues to, String key)2281 private static void copyInteger(ContentValues from, ContentValues to, String key) { 2282 to.put(key, from.getAsInteger(key)); 2283 } 2284 2285 static class SqlArguments { 2286 public final String table; 2287 public final String where; 2288 public final String[] args; 2289 SqlArguments(Uri url, String where, String[] args)2290 SqlArguments(Uri url, String where, String[] args) { 2291 if (url.getPathSegments().size() == 1) { 2292 this.table = url.getPathSegments().get(0); 2293 this.where = where; 2294 this.args = args; 2295 } else if (url.getPathSegments().size() != 2) { 2296 throw new IllegalArgumentException("Invalid URI: " + url); 2297 } else if (!TextUtils.isEmpty(where)) { 2298 throw new UnsupportedOperationException("WHERE clause not supported: " + url); 2299 } else { 2300 this.table = url.getPathSegments().get(0); 2301 this.where = "_id=" + ContentUris.parseId(url); 2302 this.args = null; 2303 } 2304 } 2305 SqlArguments(Uri url)2306 SqlArguments(Uri url) { 2307 if (url.getPathSegments().size() == 1) { 2308 table = url.getPathSegments().get(0); 2309 where = null; 2310 args = null; 2311 } else { 2312 throw new IllegalArgumentException("Invalid URI: " + url); 2313 } 2314 } 2315 } 2316 2317 static interface WorkspaceLoader { 2318 /** 2319 * @param screenIds A mutable list of screen its 2320 * @return the number of workspace items added. 2321 */ loadLayout(SQLiteDatabase db, ArrayList<Long> screenIds)2322 int loadLayout(SQLiteDatabase db, ArrayList<Long> screenIds); 2323 } 2324 2325 private static class SimpleWorkspaceLoader implements WorkspaceLoader { 2326 private final Resources mRes; 2327 private final int mWorkspaceId; 2328 private final DatabaseHelper mHelper; 2329 SimpleWorkspaceLoader(DatabaseHelper helper, Resources res, int workspaceId)2330 SimpleWorkspaceLoader(DatabaseHelper helper, Resources res, int workspaceId) { 2331 mHelper = helper; 2332 mRes = res; 2333 mWorkspaceId = workspaceId; 2334 } 2335 2336 @Override loadLayout(SQLiteDatabase db, ArrayList<Long> screenIds)2337 public int loadLayout(SQLiteDatabase db, ArrayList<Long> screenIds) { 2338 return mHelper.loadFavoritesRecursive(db, mRes, mWorkspaceId, screenIds); 2339 } 2340 } 2341 } 2342