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