blob: 5e572a5755fbb9a88cad3aadba9ba7a83853889e [file] [log] [blame]
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001/*
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
Joe Onoratoa5902522009-07-30 13:37:37 -070017package com.android.launcher2;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080018
Bjorn Bringertcd8fec02010-01-14 13:26:43 +000019import android.app.SearchManager;
The Android Open Source Project7376fae2009-03-11 12:11:58 -070020import android.appwidget.AppWidgetHost;
Mike Cleronb87bd162009-10-30 16:36:56 -070021import android.appwidget.AppWidgetManager;
Bjorn Bringertcd8fec02010-01-14 13:26:43 +000022import android.appwidget.AppWidgetProviderInfo;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080023import android.content.ComponentName;
Adam Cohen228da5a2011-07-27 22:23:47 -070024import android.content.ContentProvider;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080025import android.content.ContentResolver;
Adam Cohen228da5a2011-07-27 22:23:47 -070026import android.content.ContentUris;
27import android.content.ContentValues;
28import android.content.Context;
29import android.content.Intent;
Michael Jurkab85f8a42012-04-25 15:48:32 -070030import android.content.SharedPreferences;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080031import android.content.pm.ActivityInfo;
Adam Cohen228da5a2011-07-27 22:23:47 -070032import android.content.pm.PackageManager;
33import android.content.res.Resources;
34import android.content.res.TypedArray;
35import android.content.res.XmlResourceParser;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080036import android.database.Cursor;
37import android.database.SQLException;
Adam Cohen228da5a2011-07-27 22:23:47 -070038import android.database.sqlite.SQLiteDatabase;
39import android.database.sqlite.SQLiteOpenHelper;
40import android.database.sqlite.SQLiteQueryBuilder;
41import android.database.sqlite.SQLiteStatement;
Joe Onorato0589f0f2010-02-08 13:44:00 -080042import android.graphics.Bitmap;
43import android.graphics.BitmapFactory;
Adam Cohen228da5a2011-07-27 22:23:47 -070044import android.net.Uri;
Winson Chungb3302ae2012-05-01 10:19:14 -070045import android.os.Bundle;
Adam Cohen228da5a2011-07-27 22:23:47 -070046import android.provider.Settings;
47import android.text.TextUtils;
48import android.util.AttributeSet;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080049import android.util.Log;
50import android.util.Xml;
Adam Cohen228da5a2011-07-27 22:23:47 -070051
Michael Jurka8b805b12012-04-18 14:23:14 -070052import com.android.launcher.R;
53import com.android.launcher2.LauncherSettings.Favorites;
54
55import org.xmlpull.v1.XmlPullParser;
56import org.xmlpull.v1.XmlPullParserException;
57
The Android Open Source Project31dd5032009-03-03 19:32:27 -080058import java.io.IOException;
Mike Cleronb87bd162009-10-30 16:36:56 -070059import java.net.URISyntaxException;
Adam Cohen228da5a2011-07-27 22:23:47 -070060import java.util.ArrayList;
Bjorn Bringertcd8fec02010-01-14 13:26:43 +000061import java.util.List;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080062
The Android Open Source Project31dd5032009-03-03 19:32:27 -080063public class LauncherProvider extends ContentProvider {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -080064 private static final String TAG = "Launcher.LauncherProvider";
65 private static final boolean LOGD = false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080066
67 private static final String DATABASE_NAME = "launcher.db";
Winson Chung3d503fb2011-07-13 17:25:49 -070068
Daniel Lehmannc3a80402012-04-23 21:35:11 -070069 private static final int DATABASE_VERSION = 10;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080070
Joe Onoratoa5902522009-07-30 13:37:37 -070071 static final String AUTHORITY = "com.android.launcher2.settings";
Winson Chung3d503fb2011-07-13 17:25:49 -070072
The Android Open Source Project31dd5032009-03-03 19:32:27 -080073 static final String TABLE_FAVORITES = "favorites";
74 static final String PARAMETER_NOTIFY = "notify";
Michael Jurkab85f8a42012-04-25 15:48:32 -070075 static final String DB_CREATED_BUT_DEFAULT_WORKSPACE_NOT_LOADED =
76 "DB_CREATED_BUT_DEFAULT_WORKSPACE_NOT_LOADED";
The Android Open Source Project31dd5032009-03-03 19:32:27 -080077
Winson Chungb3302ae2012-05-01 10:19:14 -070078 private static final String ACTION_APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE =
79 "com.android.launcher.action.APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE";
80
Jeffrey Sharkey2bbcae12009-03-31 14:37:57 -070081 /**
Romain Guy73b979d2009-06-09 12:57:21 -070082 * {@link Uri} triggered at any registered {@link android.database.ContentObserver} when
Jeffrey Sharkey2bbcae12009-03-31 14:37:57 -070083 * {@link AppWidgetHost#deleteHost()} is called during database creation.
84 * Use this to recall {@link AppWidgetHost#startListening()} if needed.
85 */
86 static final Uri CONTENT_APPWIDGET_RESET_URI =
87 Uri.parse("content://" + AUTHORITY + "/appWidgetReset");
Daniel Lehmannc3a80402012-04-23 21:35:11 -070088
Michael Jurkaa8c760d2011-04-28 14:59:33 -070089 private DatabaseHelper mOpenHelper;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080090
91 @Override
92 public boolean onCreate() {
93 mOpenHelper = new DatabaseHelper(getContext());
Michael Jurkaa8c760d2011-04-28 14:59:33 -070094 ((LauncherApplication) getContext()).setLauncherProvider(this);
The Android Open Source Project31dd5032009-03-03 19:32:27 -080095 return true;
96 }
97
98 @Override
99 public String getType(Uri uri) {
100 SqlArguments args = new SqlArguments(uri, null, null);
101 if (TextUtils.isEmpty(args.where)) {
102 return "vnd.android.cursor.dir/" + args.table;
103 } else {
104 return "vnd.android.cursor.item/" + args.table;
105 }
106 }
107
108 @Override
109 public Cursor query(Uri uri, String[] projection, String selection,
110 String[] selectionArgs, String sortOrder) {
111
112 SqlArguments args = new SqlArguments(uri, selection, selectionArgs);
113 SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
114 qb.setTables(args.table);
115
Romain Guy73b979d2009-06-09 12:57:21 -0700116 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800117 Cursor result = qb.query(db, projection, args.where, args.args, null, null, sortOrder);
118 result.setNotificationUri(getContext().getContentResolver(), uri);
119
120 return result;
121 }
122
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700123 private static long dbInsertAndCheck(DatabaseHelper helper,
124 SQLiteDatabase db, String table, String nullColumnHack, ContentValues values) {
125 if (!values.containsKey(LauncherSettings.Favorites._ID)) {
126 throw new RuntimeException("Error: attempting to add item without specifying an id");
127 }
128 return db.insert(table, nullColumnHack, values);
129 }
130
Adam Cohen228da5a2011-07-27 22:23:47 -0700131 private static void deleteId(SQLiteDatabase db, long id) {
132 Uri uri = LauncherSettings.Favorites.getContentUri(id, false);
133 SqlArguments args = new SqlArguments(uri, null, null);
134 db.delete(args.table, args.where, args.args);
135 }
136
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800137 @Override
138 public Uri insert(Uri uri, ContentValues initialValues) {
139 SqlArguments args = new SqlArguments(uri);
140
141 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700142 final long rowId = dbInsertAndCheck(mOpenHelper, db, args.table, null, initialValues);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800143 if (rowId <= 0) return null;
144
145 uri = ContentUris.withAppendedId(uri, rowId);
146 sendNotify(uri);
147
148 return uri;
149 }
150
151 @Override
152 public int bulkInsert(Uri uri, ContentValues[] values) {
153 SqlArguments args = new SqlArguments(uri);
154
155 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
156 db.beginTransaction();
157 try {
158 int numValues = values.length;
159 for (int i = 0; i < numValues; i++) {
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700160 if (dbInsertAndCheck(mOpenHelper, db, args.table, null, values[i]) < 0) {
161 return 0;
162 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800163 }
164 db.setTransactionSuccessful();
165 } finally {
166 db.endTransaction();
167 }
168
169 sendNotify(uri);
170 return values.length;
171 }
172
173 @Override
174 public int delete(Uri uri, String selection, String[] selectionArgs) {
175 SqlArguments args = new SqlArguments(uri, selection, selectionArgs);
176
177 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
178 int count = db.delete(args.table, args.where, args.args);
179 if (count > 0) sendNotify(uri);
180
181 return count;
182 }
183
184 @Override
185 public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
186 SqlArguments args = new SqlArguments(uri, selection, selectionArgs);
187
188 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
189 int count = db.update(args.table, values, args.where, args.args);
190 if (count > 0) sendNotify(uri);
191
192 return count;
193 }
194
195 private void sendNotify(Uri uri) {
196 String notify = uri.getQueryParameter(PARAMETER_NOTIFY);
197 if (notify == null || "true".equals(notify)) {
198 getContext().getContentResolver().notifyChange(uri, null);
199 }
200 }
201
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700202 public long generateNewId() {
203 return mOpenHelper.generateNewId();
204 }
205
Michael Jurkab85f8a42012-04-25 15:48:32 -0700206 public void loadDefaultFavoritesIfNecessary() {
207 String spKey = LauncherApplication.getSharedPreferencesKey();
208 SharedPreferences sp = getContext().getSharedPreferences(spKey, Context.MODE_PRIVATE);
209 if (sp.getBoolean(DB_CREATED_BUT_DEFAULT_WORKSPACE_NOT_LOADED, false)) {
210 // Populate favorites table with initial favorites
211 SharedPreferences.Editor editor = sp.edit();
212 editor.remove(DB_CREATED_BUT_DEFAULT_WORKSPACE_NOT_LOADED);
213 mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(), R.xml.default_workspace);
214 editor.commit();
215 }
216 }
217
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800218 private static class DatabaseHelper extends SQLiteOpenHelper {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800219 private static final String TAG_FAVORITES = "favorites";
220 private static final String TAG_FAVORITE = "favorite";
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700221 private static final String TAG_CLOCK = "clock";
222 private static final String TAG_SEARCH = "search";
Mike Cleronb87bd162009-10-30 16:36:56 -0700223 private static final String TAG_APPWIDGET = "appwidget";
224 private static final String TAG_SHORTCUT = "shortcut";
Adam Cohen228da5a2011-07-27 22:23:47 -0700225 private static final String TAG_FOLDER = "folder";
Winson Chungb3302ae2012-05-01 10:19:14 -0700226 private static final String TAG_EXTRA = "extra";
Winson Chung3d503fb2011-07-13 17:25:49 -0700227
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800228 private final Context mContext;
The Android Open Source Project7376fae2009-03-11 12:11:58 -0700229 private final AppWidgetHost mAppWidgetHost;
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700230 private long mMaxId = -1;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800231
232 DatabaseHelper(Context context) {
233 super(context, DATABASE_NAME, null, DATABASE_VERSION);
234 mContext = context;
The Android Open Source Project7376fae2009-03-11 12:11:58 -0700235 mAppWidgetHost = new AppWidgetHost(context, Launcher.APPWIDGET_HOST_ID);
Winson Chung3d503fb2011-07-13 17:25:49 -0700236
237 // In the case where neither onCreate nor onUpgrade gets called, we read the maxId from
238 // the DB here
239 if (mMaxId == -1) {
240 mMaxId = initializeMaxId(getWritableDatabase());
241 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800242 }
243
Jeffrey Sharkey2bbcae12009-03-31 14:37:57 -0700244 /**
245 * Send notification that we've deleted the {@link AppWidgetHost},
246 * probably as part of the initial database creation. The receiver may
247 * want to re-call {@link AppWidgetHost#startListening()} to ensure
248 * callbacks are correctly set.
249 */
250 private void sendAppWidgetResetNotify() {
251 final ContentResolver resolver = mContext.getContentResolver();
252 resolver.notifyChange(CONTENT_APPWIDGET_RESET_URI, null);
253 }
254
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800255 @Override
256 public void onCreate(SQLiteDatabase db) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800257 if (LOGD) Log.d(TAG, "creating new launcher database");
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700258
259 mMaxId = 1;
260
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800261 db.execSQL("CREATE TABLE favorites (" +
262 "_id INTEGER PRIMARY KEY," +
263 "title TEXT," +
264 "intent TEXT," +
265 "container INTEGER," +
266 "screen INTEGER," +
267 "cellX INTEGER," +
268 "cellY INTEGER," +
269 "spanX INTEGER," +
270 "spanY INTEGER," +
271 "itemType INTEGER," +
The Android Open Source Projectca9475f2009-03-13 13:04:24 -0700272 "appWidgetId INTEGER NOT NULL DEFAULT -1," +
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800273 "isShortcut INTEGER," +
274 "iconType INTEGER," +
275 "iconPackage TEXT," +
276 "iconResource TEXT," +
277 "icon BLOB," +
278 "uri TEXT," +
279 "displayMode INTEGER" +
280 ");");
281
The Android Open Source Project7376fae2009-03-11 12:11:58 -0700282 // Database was just created, so wipe any previous widgets
283 if (mAppWidgetHost != null) {
284 mAppWidgetHost.deleteHost();
Jeffrey Sharkey2bbcae12009-03-31 14:37:57 -0700285 sendAppWidgetResetNotify();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800286 }
Winson Chung3d503fb2011-07-13 17:25:49 -0700287
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800288 if (!convertDatabase(db)) {
Michael Jurkab85f8a42012-04-25 15:48:32 -0700289 // Set a shared pref so that we know we need to load the default workspace later
290 setFlagToLoadDefaultWorkspaceLater();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800291 }
292 }
293
Michael Jurkab85f8a42012-04-25 15:48:32 -0700294 private void setFlagToLoadDefaultWorkspaceLater() {
295 String spKey = LauncherApplication.getSharedPreferencesKey();
296 SharedPreferences sp = mContext.getSharedPreferences(spKey, Context.MODE_PRIVATE);
297 SharedPreferences.Editor editor = sp.edit();
298 editor.putBoolean(DB_CREATED_BUT_DEFAULT_WORKSPACE_NOT_LOADED, true);
299 editor.commit();
300 }
301
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800302 private boolean convertDatabase(SQLiteDatabase db) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800303 if (LOGD) Log.d(TAG, "converting database from an older format, but not onUpgrade");
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800304 boolean converted = false;
305
306 final Uri uri = Uri.parse("content://" + Settings.AUTHORITY +
307 "/old_favorites?notify=true");
308 final ContentResolver resolver = mContext.getContentResolver();
309 Cursor cursor = null;
310
311 try {
312 cursor = resolver.query(uri, null, null, null, null);
313 } catch (Exception e) {
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700314 // Ignore
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800315 }
316
317 // We already have a favorites database in the old provider
318 if (cursor != null && cursor.getCount() > 0) {
319 try {
320 converted = copyFromCursor(db, cursor) > 0;
321 } finally {
322 cursor.close();
323 }
324
325 if (converted) {
326 resolver.delete(uri, null, null);
327 }
328 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700329
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800330 if (converted) {
The Android Open Source Project7376fae2009-03-11 12:11:58 -0700331 // Convert widgets from this import into widgets
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800332 if (LOGD) Log.d(TAG, "converted and now triggering widget upgrade");
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800333 convertWidgets(db);
334 }
335
336 return converted;
337 }
338
339 private int copyFromCursor(SQLiteDatabase db, Cursor c) {
Romain Guy73b979d2009-06-09 12:57:21 -0700340 final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800341 final int intentIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.INTENT);
342 final int titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE);
343 final int iconTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_TYPE);
344 final int iconIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON);
345 final int iconPackageIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_PACKAGE);
346 final int iconResourceIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_RESOURCE);
347 final int containerIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER);
348 final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE);
349 final int screenIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN);
350 final int cellXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX);
351 final int cellYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY);
352 final int uriIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.URI);
353 final int displayModeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.DISPLAY_MODE);
354
355 ContentValues[] rows = new ContentValues[c.getCount()];
356 int i = 0;
357 while (c.moveToNext()) {
358 ContentValues values = new ContentValues(c.getColumnCount());
Romain Guy73b979d2009-06-09 12:57:21 -0700359 values.put(LauncherSettings.Favorites._ID, c.getLong(idIndex));
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800360 values.put(LauncherSettings.Favorites.INTENT, c.getString(intentIndex));
361 values.put(LauncherSettings.Favorites.TITLE, c.getString(titleIndex));
362 values.put(LauncherSettings.Favorites.ICON_TYPE, c.getInt(iconTypeIndex));
363 values.put(LauncherSettings.Favorites.ICON, c.getBlob(iconIndex));
364 values.put(LauncherSettings.Favorites.ICON_PACKAGE, c.getString(iconPackageIndex));
365 values.put(LauncherSettings.Favorites.ICON_RESOURCE, c.getString(iconResourceIndex));
366 values.put(LauncherSettings.Favorites.CONTAINER, c.getInt(containerIndex));
367 values.put(LauncherSettings.Favorites.ITEM_TYPE, c.getInt(itemTypeIndex));
The Android Open Source Project7376fae2009-03-11 12:11:58 -0700368 values.put(LauncherSettings.Favorites.APPWIDGET_ID, -1);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800369 values.put(LauncherSettings.Favorites.SCREEN, c.getInt(screenIndex));
370 values.put(LauncherSettings.Favorites.CELLX, c.getInt(cellXIndex));
371 values.put(LauncherSettings.Favorites.CELLY, c.getInt(cellYIndex));
372 values.put(LauncherSettings.Favorites.URI, c.getString(uriIndex));
373 values.put(LauncherSettings.Favorites.DISPLAY_MODE, c.getInt(displayModeIndex));
374 rows[i++] = values;
375 }
376
377 db.beginTransaction();
378 int total = 0;
379 try {
380 int numValues = rows.length;
381 for (i = 0; i < numValues; i++) {
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700382 if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, rows[i]) < 0) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800383 return 0;
384 } else {
385 total++;
386 }
387 }
388 db.setTransactionSuccessful();
389 } finally {
390 db.endTransaction();
391 }
392
393 return total;
394 }
395
396 @Override
397 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800398 if (LOGD) Log.d(TAG, "onUpgrade triggered");
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700399
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800400 int version = oldVersion;
The Android Open Source Projectca9475f2009-03-13 13:04:24 -0700401 if (version < 3) {
402 // upgrade 1,2 -> 3 added appWidgetId column
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800403 db.beginTransaction();
404 try {
The Android Open Source Project7376fae2009-03-11 12:11:58 -0700405 // Insert new column for holding appWidgetIds
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800406 db.execSQL("ALTER TABLE favorites " +
The Android Open Source Projectca9475f2009-03-13 13:04:24 -0700407 "ADD COLUMN appWidgetId INTEGER NOT NULL DEFAULT -1;");
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800408 db.setTransactionSuccessful();
The Android Open Source Projectca9475f2009-03-13 13:04:24 -0700409 version = 3;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800410 } catch (SQLException ex) {
411 // Old version remains, which means we wipe old data
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800412 Log.e(TAG, ex.getMessage(), ex);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800413 } finally {
414 db.endTransaction();
415 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700416
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800417 // Convert existing widgets only if table upgrade was successful
The Android Open Source Projectca9475f2009-03-13 13:04:24 -0700418 if (version == 3) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800419 convertWidgets(db);
420 }
421 }
Romain Guy73b979d2009-06-09 12:57:21 -0700422
423 if (version < 4) {
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800424 version = 4;
Romain Guy73b979d2009-06-09 12:57:21 -0700425 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700426
Romain Guy509cd6a2010-03-23 15:10:56 -0700427 // Where's version 5?
428 // - Donut and sholes on 2.0 shipped with version 4 of launcher1.
429 // - Passion shipped on 2.1 with version 6 of launcher2
430 // - Sholes shipped on 2.1r1 (aka Mr. 3) with version 5 of launcher 1
431 // but version 5 on there was the updateContactsShortcuts change
432 // which was version 6 in launcher 2 (first shipped on passion 2.1r1).
433 // The updateContactsShortcuts change is idempotent, so running it twice
434 // is okay so we'll do that when upgrading the devices that shipped with it.
435 if (version < 6) {
Mike Cleron3a2b3f22009-11-05 17:17:42 -0800436 // We went from 3 to 5 screens. Move everything 1 to the right
437 db.beginTransaction();
438 try {
439 db.execSQL("UPDATE favorites SET screen=(screen + 1);");
440 db.setTransactionSuccessful();
Mike Cleron3a2b3f22009-11-05 17:17:42 -0800441 } catch (SQLException ex) {
442 // Old version remains, which means we wipe old data
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800443 Log.e(TAG, ex.getMessage(), ex);
Mike Cleron3a2b3f22009-11-05 17:17:42 -0800444 } finally {
445 db.endTransaction();
446 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700447
Romain Guy509cd6a2010-03-23 15:10:56 -0700448 // We added the fast track.
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800449 if (updateContactsShortcuts(db)) {
450 version = 6;
451 }
452 }
Bjorn Bringert7984c942009-12-09 15:38:25 +0000453
454 if (version < 7) {
455 // Version 7 gets rid of the special search widget.
456 convertWidgets(db);
457 version = 7;
458 }
459
Joe Onorato0589f0f2010-02-08 13:44:00 -0800460 if (version < 8) {
461 // Version 8 (froyo) has the icons all normalized. This should
462 // already be the case in practice, but we now rely on it and don't
463 // resample the images each time.
464 normalizeIcons(db);
465 version = 8;
466 }
467
Winson Chung3d503fb2011-07-13 17:25:49 -0700468 if (version < 9) {
469 // The max id is not yet set at this point (onUpgrade is triggered in the ctor
470 // before it gets a change to get set, so we need to read it here when we use it)
471 if (mMaxId == -1) {
472 mMaxId = initializeMaxId(db);
473 }
474
475 // Add default hotseat icons
Winson Chung6d092682011-11-16 18:43:26 -0800476 loadFavorites(db, R.xml.update_workspace);
Winson Chung3d503fb2011-07-13 17:25:49 -0700477 version = 9;
478 }
479
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700480 if (version < 10) {
481 // Contact shortcuts need a different set of flags to be launched now
482 // The updateContactsShortcuts change is idempotent, so we can keep using it like
483 // back in the Donut days
484 updateContactsShortcuts(db);
485 version = 10;
486 }
487
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800488 if (version != DATABASE_VERSION) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800489 Log.w(TAG, "Destroying all old data.");
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800490 db.execSQL("DROP TABLE IF EXISTS " + TABLE_FAVORITES);
491 onCreate(db);
492 }
493 }
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800494
495 private boolean updateContactsShortcuts(SQLiteDatabase db) {
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800496 final String selectWhere = buildOrWhereString(Favorites.ITEM_TYPE,
497 new int[] { Favorites.ITEM_TYPE_SHORTCUT });
498
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700499 Cursor c = null;
500 final String actionQuickContact = "com.android.contacts.action.QUICK_CONTACT";
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800501 db.beginTransaction();
502 try {
503 // Select and iterate through each matching widget
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700504 c = db.query(TABLE_FAVORITES,
505 new String[] { Favorites._ID, Favorites.INTENT },
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800506 selectWhere, null, null, null, null);
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700507 if (c == null) return false;
508
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800509 if (LOGD) Log.d(TAG, "found upgrade cursor count=" + c.getCount());
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700510
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800511 final int idIndex = c.getColumnIndex(Favorites._ID);
512 final int intentIndex = c.getColumnIndex(Favorites.INTENT);
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700513
514 while (c.moveToNext()) {
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800515 long favoriteId = c.getLong(idIndex);
516 final String intentUri = c.getString(intentIndex);
517 if (intentUri != null) {
518 try {
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700519 final Intent intent = Intent.parseUri(intentUri, 0);
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800520 android.util.Log.d("Home", intent.toString());
521 final Uri uri = intent.getData();
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700522 if (uri != null) {
523 final String data = uri.toString();
524 if ((Intent.ACTION_VIEW.equals(intent.getAction()) ||
525 actionQuickContact.equals(intent.getAction())) &&
526 (data.startsWith("content://contacts/people/") ||
527 data.startsWith("content://com.android.contacts/" +
528 "contacts/lookup/"))) {
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800529
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700530 final Intent newIntent = new Intent(actionQuickContact);
531 // When starting from the launcher, start in a new, cleared task
532 // CLEAR_WHEN_TASK_RESET cannot reset the root of a task, so we
533 // clear the whole thing preemptively here since
534 // QuickContactActivity will finish itself when launching other
535 // detail activities.
536 newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
537 Intent.FLAG_ACTIVITY_CLEAR_TASK);
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800538
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700539 newIntent.setData(uri);
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800540
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700541 final ContentValues values = new ContentValues();
542 values.put(LauncherSettings.Favorites.INTENT,
543 newIntent.toUri(0));
544
545 String updateWhere = Favorites._ID + "=" + favoriteId;
546 db.update(TABLE_FAVORITES, values, updateWhere, null);
547 }
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800548 }
549 } catch (RuntimeException ex) {
550 Log.e(TAG, "Problem upgrading shortcut", ex);
551 } catch (URISyntaxException e) {
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700552 Log.e(TAG, "Problem upgrading shortcut", e);
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800553 }
554 }
555 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700556
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800557 db.setTransactionSuccessful();
558 } catch (SQLException ex) {
559 Log.w(TAG, "Problem while upgrading contacts", ex);
560 return false;
561 } finally {
562 db.endTransaction();
563 if (c != null) {
564 c.close();
565 }
566 }
567
568 return true;
569 }
570
Joe Onorato0589f0f2010-02-08 13:44:00 -0800571 private void normalizeIcons(SQLiteDatabase db) {
572 Log.d(TAG, "normalizing icons");
573
Joe Onorato346e1292010-02-18 10:34:24 -0500574 db.beginTransaction();
Joe Onorato0589f0f2010-02-08 13:44:00 -0800575 Cursor c = null;
Joe Onorato9690b392010-03-23 17:34:37 -0400576 SQLiteStatement update = null;
Joe Onorato0589f0f2010-02-08 13:44:00 -0800577 try {
578 boolean logged = false;
Joe Onorato9690b392010-03-23 17:34:37 -0400579 update = db.compileStatement("UPDATE favorites "
Jeff Hamiltoneaf77d62010-02-13 00:08:17 -0600580 + "SET icon=? WHERE _id=?");
Joe Onorato0589f0f2010-02-08 13:44:00 -0800581
582 c = db.rawQuery("SELECT _id, icon FROM favorites WHERE iconType=" +
583 Favorites.ICON_TYPE_BITMAP, null);
584
585 final int idIndex = c.getColumnIndexOrThrow(Favorites._ID);
586 final int iconIndex = c.getColumnIndexOrThrow(Favorites.ICON);
587
588 while (c.moveToNext()) {
589 long id = c.getLong(idIndex);
590 byte[] data = c.getBlob(iconIndex);
591 try {
592 Bitmap bitmap = Utilities.resampleIconBitmap(
593 BitmapFactory.decodeByteArray(data, 0, data.length),
594 mContext);
595 if (bitmap != null) {
596 update.bindLong(1, id);
597 data = ItemInfo.flattenBitmap(bitmap);
598 if (data != null) {
599 update.bindBlob(2, data);
600 update.execute();
601 }
602 bitmap.recycle();
Joe Onorato0589f0f2010-02-08 13:44:00 -0800603 }
604 } catch (Exception e) {
605 if (!logged) {
606 Log.e(TAG, "Failed normalizing icon " + id, e);
607 } else {
608 Log.e(TAG, "Also failed normalizing icon " + id);
609 }
610 logged = true;
611 }
612 }
Bjorn Bringert3a928e42010-02-19 11:15:40 +0000613 db.setTransactionSuccessful();
Joe Onorato0589f0f2010-02-08 13:44:00 -0800614 } catch (SQLException ex) {
615 Log.w(TAG, "Problem while allocating appWidgetIds for existing widgets", ex);
616 } finally {
617 db.endTransaction();
Joe Onorato9690b392010-03-23 17:34:37 -0400618 if (update != null) {
619 update.close();
620 }
Joe Onorato0589f0f2010-02-08 13:44:00 -0800621 if (c != null) {
622 c.close();
623 }
624 }
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700625 }
626
627 // Generates a new ID to use for an object in your database. This method should be only
628 // called from the main UI thread. As an exception, we do call it when we call the
629 // constructor from the worker thread; however, this doesn't extend until after the
630 // constructor is called, and we only pass a reference to LauncherProvider to LauncherApp
631 // after that point
632 public long generateNewId() {
633 if (mMaxId < 0) {
634 throw new RuntimeException("Error: max id was not initialized");
635 }
636 mMaxId += 1;
637 return mMaxId;
638 }
639
640 private long initializeMaxId(SQLiteDatabase db) {
641 Cursor c = db.rawQuery("SELECT MAX(_id) FROM favorites", null);
642
643 // get the result
644 final int maxIdIndex = 0;
645 long id = -1;
646 if (c != null && c.moveToNext()) {
647 id = c.getLong(maxIdIndex);
648 }
Michael Jurka5130e402011-10-13 04:55:35 -0700649 if (c != null) {
650 c.close();
651 }
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700652
653 if (id == -1) {
654 throw new RuntimeException("Error: could not query max id");
655 }
656
657 return id;
Joe Onorato0589f0f2010-02-08 13:44:00 -0800658 }
659
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800660 /**
The Android Open Source Project7376fae2009-03-11 12:11:58 -0700661 * Upgrade existing clock and photo frame widgets into their new widget
Bjorn Bringert93c45762009-12-16 13:19:47 +0000662 * equivalents.
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800663 */
664 private void convertWidgets(SQLiteDatabase db) {
Bjorn Bringert34251342009-12-15 13:33:11 +0000665 final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800666 final int[] bindSources = new int[] {
667 Favorites.ITEM_TYPE_WIDGET_CLOCK,
668 Favorites.ITEM_TYPE_WIDGET_PHOTO_FRAME,
Bjorn Bringert7984c942009-12-09 15:38:25 +0000669 Favorites.ITEM_TYPE_WIDGET_SEARCH,
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800670 };
Bjorn Bringert7984c942009-12-09 15:38:25 +0000671
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800672 final String selectWhere = buildOrWhereString(Favorites.ITEM_TYPE, bindSources);
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700673
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800674 Cursor c = null;
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700675
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800676 db.beginTransaction();
677 try {
678 // Select and iterate through each matching widget
Bjorn Bringert7984c942009-12-09 15:38:25 +0000679 c = db.query(TABLE_FAVORITES, new String[] { Favorites._ID, Favorites.ITEM_TYPE },
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800680 selectWhere, null, null, null, null);
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700681
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800682 if (LOGD) Log.d(TAG, "found upgrade cursor count=" + c.getCount());
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700683
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800684 final ContentValues values = new ContentValues();
685 while (c != null && c.moveToNext()) {
686 long favoriteId = c.getLong(0);
Bjorn Bringert7984c942009-12-09 15:38:25 +0000687 int favoriteType = c.getInt(1);
688
The Android Open Source Project7376fae2009-03-11 12:11:58 -0700689 // Allocate and update database with new appWidgetId
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800690 try {
The Android Open Source Project7376fae2009-03-11 12:11:58 -0700691 int appWidgetId = mAppWidgetHost.allocateAppWidgetId();
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700692
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800693 if (LOGD) {
694 Log.d(TAG, "allocated appWidgetId=" + appWidgetId
695 + " for favoriteId=" + favoriteId);
696 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800697 values.clear();
Bjorn Bringert7984c942009-12-09 15:38:25 +0000698 values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_APPWIDGET);
699 values.put(Favorites.APPWIDGET_ID, appWidgetId);
700
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800701 // Original widgets might not have valid spans when upgrading
Bjorn Bringert7984c942009-12-09 15:38:25 +0000702 if (favoriteType == Favorites.ITEM_TYPE_WIDGET_SEARCH) {
703 values.put(LauncherSettings.Favorites.SPANX, 4);
704 values.put(LauncherSettings.Favorites.SPANY, 1);
705 } else {
706 values.put(LauncherSettings.Favorites.SPANX, 2);
707 values.put(LauncherSettings.Favorites.SPANY, 2);
708 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800709
710 String updateWhere = Favorites._ID + "=" + favoriteId;
711 db.update(TABLE_FAVORITES, values, updateWhere, null);
Bjorn Bringert34251342009-12-15 13:33:11 +0000712
Bjorn Bringert34251342009-12-15 13:33:11 +0000713 if (favoriteType == Favorites.ITEM_TYPE_WIDGET_CLOCK) {
Michael Jurka8b805b12012-04-18 14:23:14 -0700714 // TODO: check return value
715 appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId,
Bjorn Bringert34251342009-12-15 13:33:11 +0000716 new ComponentName("com.android.alarmclock",
717 "com.android.alarmclock.AnalogAppWidgetProvider"));
718 } else if (favoriteType == Favorites.ITEM_TYPE_WIDGET_PHOTO_FRAME) {
Michael Jurka8b805b12012-04-18 14:23:14 -0700719 // TODO: check return value
720 appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId,
Bjorn Bringert34251342009-12-15 13:33:11 +0000721 new ComponentName("com.android.camera",
722 "com.android.camera.PhotoAppWidgetProvider"));
723 } else if (favoriteType == Favorites.ITEM_TYPE_WIDGET_SEARCH) {
Michael Jurka8b805b12012-04-18 14:23:14 -0700724 // TODO: check return value
725 appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId,
Bjorn Bringertcd8fec02010-01-14 13:26:43 +0000726 getSearchWidgetProvider());
Bjorn Bringert34251342009-12-15 13:33:11 +0000727 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800728 } catch (RuntimeException ex) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800729 Log.e(TAG, "Problem allocating appWidgetId", ex);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800730 }
731 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700732
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800733 db.setTransactionSuccessful();
734 } catch (SQLException ex) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800735 Log.w(TAG, "Problem while allocating appWidgetIds for existing widgets", ex);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800736 } finally {
737 db.endTransaction();
738 if (c != null) {
739 c.close();
740 }
741 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800742 }
743
Michael Jurka8b805b12012-04-18 14:23:14 -0700744 private static final void beginDocument(XmlPullParser parser, String firstElementName)
745 throws XmlPullParserException, IOException {
746 int type;
747 while ((type = parser.next()) != parser.START_TAG
748 && type != parser.END_DOCUMENT) {
749 ;
750 }
751
752 if (type != parser.START_TAG) {
753 throw new XmlPullParserException("No start tag found");
754 }
755
756 if (!parser.getName().equals(firstElementName)) {
757 throw new XmlPullParserException("Unexpected start tag: found " + parser.getName() +
758 ", expected " + firstElementName);
759 }
760 }
761
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800762 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800763 * Loads the default set of favorite packages from an xml file.
764 *
765 * @param db The database to write the values into
Winson Chung3d503fb2011-07-13 17:25:49 -0700766 * @param filterContainerId The specific container id of items to load
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800767 */
Winson Chung6d092682011-11-16 18:43:26 -0800768 private int loadFavorites(SQLiteDatabase db, int workspaceResourceId) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800769 Intent intent = new Intent(Intent.ACTION_MAIN, null);
770 intent.addCategory(Intent.CATEGORY_LAUNCHER);
771 ContentValues values = new ContentValues();
772
773 PackageManager packageManager = mContext.getPackageManager();
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800774 int allAppsButtonRank =
775 mContext.getResources().getInteger(R.integer.hotseat_all_apps_index);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800776 int i = 0;
777 try {
Winson Chung6d092682011-11-16 18:43:26 -0800778 XmlResourceParser parser = mContext.getResources().getXml(workspaceResourceId);
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700779 AttributeSet attrs = Xml.asAttributeSet(parser);
Michael Jurka8b805b12012-04-18 14:23:14 -0700780 beginDocument(parser, TAG_FAVORITES);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800781
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700782 final int depth = parser.getDepth();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800783
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700784 int type;
785 while (((type = parser.next()) != XmlPullParser.END_TAG ||
786 parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
787
788 if (type != XmlPullParser.START_TAG) {
789 continue;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800790 }
791
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700792 boolean added = false;
793 final String name = parser.getName();
794
795 TypedArray a = mContext.obtainStyledAttributes(attrs, R.styleable.Favorite);
796
Winson Chung3d503fb2011-07-13 17:25:49 -0700797 long container = LauncherSettings.Favorites.CONTAINER_DESKTOP;
798 if (a.hasValue(R.styleable.Favorite_container)) {
799 container = Long.valueOf(a.getString(R.styleable.Favorite_container));
800 }
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700801
Winson Chung6d092682011-11-16 18:43:26 -0800802 String screen = a.getString(R.styleable.Favorite_screen);
803 String x = a.getString(R.styleable.Favorite_x);
804 String y = a.getString(R.styleable.Favorite_y);
805
806 // If we are adding to the hotseat, the screen is used as the position in the
807 // hotseat. This screen can't be at position 0 because AllApps is in the
808 // zeroth position.
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800809 if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT
810 && Integer.valueOf(screen) == allAppsButtonRank) {
Winson Chung6d092682011-11-16 18:43:26 -0800811 throw new RuntimeException("Invalid screen position for hotseat item");
812 }
813
814 values.clear();
815 values.put(LauncherSettings.Favorites.CONTAINER, container);
816 values.put(LauncherSettings.Favorites.SCREEN, screen);
817 values.put(LauncherSettings.Favorites.CELLX, x);
818 values.put(LauncherSettings.Favorites.CELLY, y);
819
820 if (TAG_FAVORITE.equals(name)) {
821 long id = addAppShortcut(db, values, a, packageManager, intent);
822 added = id >= 0;
823 } else if (TAG_SEARCH.equals(name)) {
824 added = addSearchWidget(db, values);
825 } else if (TAG_CLOCK.equals(name)) {
826 added = addClockWidget(db, values);
827 } else if (TAG_APPWIDGET.equals(name)) {
Winson Chungb3302ae2012-05-01 10:19:14 -0700828 added = addAppWidget(parser, attrs, type, db, values, a, packageManager);
Winson Chung6d092682011-11-16 18:43:26 -0800829 } else if (TAG_SHORTCUT.equals(name)) {
830 long id = addUriShortcut(db, values, a);
831 added = id >= 0;
832 } else if (TAG_FOLDER.equals(name)) {
833 String title;
834 int titleResId = a.getResourceId(R.styleable.Favorite_title, -1);
835 if (titleResId != -1) {
836 title = mContext.getResources().getString(titleResId);
837 } else {
838 title = mContext.getResources().getString(R.string.folder_name);
Winson Chung3d503fb2011-07-13 17:25:49 -0700839 }
Winson Chung6d092682011-11-16 18:43:26 -0800840 values.put(LauncherSettings.Favorites.TITLE, title);
841 long folderId = addFolder(db, values);
842 added = folderId >= 0;
Winson Chung3d503fb2011-07-13 17:25:49 -0700843
Winson Chung6d092682011-11-16 18:43:26 -0800844 ArrayList<Long> folderItems = new ArrayList<Long>();
Winson Chung3d503fb2011-07-13 17:25:49 -0700845
Winson Chung6d092682011-11-16 18:43:26 -0800846 int folderDepth = parser.getDepth();
847 while ((type = parser.next()) != XmlPullParser.END_TAG ||
848 parser.getDepth() > folderDepth) {
849 if (type != XmlPullParser.START_TAG) {
850 continue;
851 }
852 final String folder_item_name = parser.getName();
853
854 TypedArray ar = mContext.obtainStyledAttributes(attrs,
855 R.styleable.Favorite);
856 values.clear();
857 values.put(LauncherSettings.Favorites.CONTAINER, folderId);
858
859 if (TAG_FAVORITE.equals(folder_item_name) && folderId >= 0) {
860 long id =
861 addAppShortcut(db, values, ar, packageManager, intent);
862 if (id >= 0) {
863 folderItems.add(id);
864 }
865 } else if (TAG_SHORTCUT.equals(folder_item_name) && folderId >= 0) {
866 long id = addUriShortcut(db, values, ar);
867 if (id >= 0) {
868 folderItems.add(id);
869 }
Adam Cohen228da5a2011-07-27 22:23:47 -0700870 } else {
Winson Chung6d092682011-11-16 18:43:26 -0800871 throw new RuntimeException("Folders can " +
872 "contain only shortcuts");
Adam Cohen228da5a2011-07-27 22:23:47 -0700873 }
Winson Chung6d092682011-11-16 18:43:26 -0800874 ar.recycle();
875 }
876 // We can only have folders with >= 2 items, so we need to remove the
877 // folder and clean up if less than 2 items were included, or some
878 // failed to add, and less than 2 were actually added
879 if (folderItems.size() < 2 && folderId >= 0) {
880 // We just delete the folder and any items that made it
881 deleteId(db, folderId);
882 if (folderItems.size() > 0) {
883 deleteId(db, folderItems.get(0));
Adam Cohen228da5a2011-07-27 22:23:47 -0700884 }
Winson Chung6d092682011-11-16 18:43:26 -0800885 added = false;
Winson Chung3d503fb2011-07-13 17:25:49 -0700886 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800887 }
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700888 if (added) i++;
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700889 a.recycle();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800890 }
891 } catch (XmlPullParserException e) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800892 Log.w(TAG, "Got exception parsing favorites.", e);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800893 } catch (IOException e) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800894 Log.w(TAG, "Got exception parsing favorites.", e);
Winson Chung3d503fb2011-07-13 17:25:49 -0700895 } catch (RuntimeException e) {
896 Log.w(TAG, "Got exception parsing favorites.", e);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800897 }
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700898
899 return i;
900 }
901
Adam Cohen228da5a2011-07-27 22:23:47 -0700902 private long addAppShortcut(SQLiteDatabase db, ContentValues values, TypedArray a,
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700903 PackageManager packageManager, Intent intent) {
Adam Cohen228da5a2011-07-27 22:23:47 -0700904 long id = -1;
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700905 ActivityInfo info;
906 String packageName = a.getString(R.styleable.Favorite_packageName);
907 String className = a.getString(R.styleable.Favorite_className);
908 try {
Romain Guy693599f2010-03-23 10:58:18 -0700909 ComponentName cn;
910 try {
911 cn = new ComponentName(packageName, className);
912 info = packageManager.getActivityInfo(cn, 0);
913 } catch (PackageManager.NameNotFoundException nnfe) {
914 String[] packages = packageManager.currentToCanonicalPackageNames(
915 new String[] { packageName });
916 cn = new ComponentName(packages[0], className);
917 info = packageManager.getActivityInfo(cn, 0);
918 }
Adam Cohen228da5a2011-07-27 22:23:47 -0700919 id = generateNewId();
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700920 intent.setComponent(cn);
Romain Guy693599f2010-03-23 10:58:18 -0700921 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
922 Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
Romain Guy1ce1a242009-06-23 17:34:54 -0700923 values.put(Favorites.INTENT, intent.toUri(0));
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700924 values.put(Favorites.TITLE, info.loadLabel(packageManager).toString());
925 values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_APPLICATION);
926 values.put(Favorites.SPANX, 1);
927 values.put(Favorites.SPANY, 1);
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700928 values.put(Favorites._ID, generateNewId());
Adam Cohen228da5a2011-07-27 22:23:47 -0700929 if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values) < 0) {
930 return -1;
931 }
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700932 } catch (PackageManager.NameNotFoundException e) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800933 Log.w(TAG, "Unable to add favorite: " + packageName +
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700934 "/" + className, e);
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700935 }
Adam Cohen228da5a2011-07-27 22:23:47 -0700936 return id;
937 }
938
939 private long addFolder(SQLiteDatabase db, ContentValues values) {
940 values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_FOLDER);
941 values.put(Favorites.SPANX, 1);
942 values.put(Favorites.SPANY, 1);
943 long id = generateNewId();
944 values.put(Favorites._ID, id);
945 if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values) <= 0) {
946 return -1;
947 } else {
948 return id;
949 }
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700950 }
951
Bjorn Bringertcd8fec02010-01-14 13:26:43 +0000952 private ComponentName getSearchWidgetProvider() {
953 SearchManager searchManager =
954 (SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE);
955 ComponentName searchComponent = searchManager.getGlobalSearchActivity();
956 if (searchComponent == null) return null;
957 return getProviderInPackage(searchComponent.getPackageName());
958 }
959
960 /**
961 * Gets an appwidget provider from the given package. If the package contains more than
962 * one appwidget provider, an arbitrary one is returned.
963 */
964 private ComponentName getProviderInPackage(String packageName) {
965 AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext);
966 List<AppWidgetProviderInfo> providers = appWidgetManager.getInstalledProviders();
967 if (providers == null) return null;
968 final int providerCount = providers.size();
969 for (int i = 0; i < providerCount; i++) {
970 ComponentName provider = providers.get(i).provider;
971 if (provider != null && provider.getPackageName().equals(packageName)) {
972 return provider;
973 }
974 }
975 return null;
976 }
977
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700978 private boolean addSearchWidget(SQLiteDatabase db, ContentValues values) {
Bjorn Bringertcd8fec02010-01-14 13:26:43 +0000979 ComponentName cn = getSearchWidgetProvider();
Winson Chungb3302ae2012-05-01 10:19:14 -0700980 return addAppWidget(db, values, cn, 4, 1, null);
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700981 }
982
983 private boolean addClockWidget(SQLiteDatabase db, ContentValues values) {
Bjorn Bringert34251342009-12-15 13:33:11 +0000984 ComponentName cn = new ComponentName("com.android.alarmclock",
985 "com.android.alarmclock.AnalogAppWidgetProvider");
Winson Chungb3302ae2012-05-01 10:19:14 -0700986 return addAppWidget(db, values, cn, 2, 2, null);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800987 }
Adam Cohen228da5a2011-07-27 22:23:47 -0700988
Winson Chungb3302ae2012-05-01 10:19:14 -0700989 private boolean addAppWidget(XmlResourceParser parser, AttributeSet attrs, int type,
990 SQLiteDatabase db, ContentValues values, TypedArray a,
991 PackageManager packageManager) throws XmlPullParserException, IOException {
Romain Guy693599f2010-03-23 10:58:18 -0700992
Mike Cleronb87bd162009-10-30 16:36:56 -0700993 String packageName = a.getString(R.styleable.Favorite_packageName);
994 String className = a.getString(R.styleable.Favorite_className);
995
996 if (packageName == null || className == null) {
997 return false;
998 }
Romain Guy693599f2010-03-23 10:58:18 -0700999
1000 boolean hasPackage = true;
Mike Cleronb87bd162009-10-30 16:36:56 -07001001 ComponentName cn = new ComponentName(packageName, className);
Romain Guy693599f2010-03-23 10:58:18 -07001002 try {
1003 packageManager.getReceiverInfo(cn, 0);
1004 } catch (Exception e) {
1005 String[] packages = packageManager.currentToCanonicalPackageNames(
1006 new String[] { packageName });
1007 cn = new ComponentName(packages[0], className);
1008 try {
1009 packageManager.getReceiverInfo(cn, 0);
1010 } catch (Exception e1) {
1011 hasPackage = false;
1012 }
1013 }
1014
1015 if (hasPackage) {
1016 int spanX = a.getInt(R.styleable.Favorite_spanX, 0);
1017 int spanY = a.getInt(R.styleable.Favorite_spanY, 0);
Winson Chungb3302ae2012-05-01 10:19:14 -07001018
1019 // Read the extras
1020 Bundle extras = new Bundle();
1021 int widgetDepth = parser.getDepth();
1022 while ((type = parser.next()) != XmlPullParser.END_TAG ||
1023 parser.getDepth() > widgetDepth) {
1024 if (type != XmlPullParser.START_TAG) {
1025 continue;
1026 }
1027
1028 TypedArray ar = mContext.obtainStyledAttributes(attrs, R.styleable.Extra);
1029 if (TAG_EXTRA.equals(parser.getName())) {
1030 String key = ar.getString(R.styleable.Extra_key);
1031 String value = ar.getString(R.styleable.Extra_value);
1032 if (key != null && value != null) {
1033 extras.putString(key, value);
1034 } else {
1035 throw new RuntimeException("Widget extras must have a key and value");
1036 }
1037 } else {
1038 throw new RuntimeException("Widgets can contain only extras");
1039 }
1040 ar.recycle();
1041 }
1042
1043 return addAppWidget(db, values, cn, spanX, spanY, extras);
Romain Guy693599f2010-03-23 10:58:18 -07001044 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -07001045
Romain Guy693599f2010-03-23 10:58:18 -07001046 return false;
Bjorn Bringert7984c942009-12-09 15:38:25 +00001047 }
1048
1049 private boolean addAppWidget(SQLiteDatabase db, ContentValues values, ComponentName cn,
Winson Chungb3302ae2012-05-01 10:19:14 -07001050 int spanX, int spanY, Bundle extras) {
Mike Cleronb87bd162009-10-30 16:36:56 -07001051 boolean allocatedAppWidgets = false;
1052 final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext);
1053
1054 try {
1055 int appWidgetId = mAppWidgetHost.allocateAppWidgetId();
Daniel Lehmannc3a80402012-04-23 21:35:11 -07001056
Mike Cleronb87bd162009-10-30 16:36:56 -07001057 values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_APPWIDGET);
Bjorn Bringert7984c942009-12-09 15:38:25 +00001058 values.put(Favorites.SPANX, spanX);
1059 values.put(Favorites.SPANY, spanY);
Mike Cleronb87bd162009-10-30 16:36:56 -07001060 values.put(Favorites.APPWIDGET_ID, appWidgetId);
Michael Jurkaa8c760d2011-04-28 14:59:33 -07001061 values.put(Favorites._ID, generateNewId());
1062 dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values);
Mike Cleronb87bd162009-10-30 16:36:56 -07001063
1064 allocatedAppWidgets = true;
Daniel Lehmannc3a80402012-04-23 21:35:11 -07001065
Michael Jurka8b805b12012-04-18 14:23:14 -07001066 // TODO: need to check return value
1067 appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, cn);
Winson Chungb3302ae2012-05-01 10:19:14 -07001068
1069 // Send a broadcast to configure the widget
1070 if (extras != null && !extras.isEmpty()) {
1071 Intent intent = new Intent(ACTION_APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE);
1072 intent.setComponent(cn);
1073 intent.putExtras(extras);
1074 intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
1075 mContext.sendBroadcast(intent);
1076 }
Mike Cleronb87bd162009-10-30 16:36:56 -07001077 } catch (RuntimeException ex) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -08001078 Log.e(TAG, "Problem allocating appWidgetId", ex);
Mike Cleronb87bd162009-10-30 16:36:56 -07001079 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -07001080
Mike Cleronb87bd162009-10-30 16:36:56 -07001081 return allocatedAppWidgets;
1082 }
Adam Cohen228da5a2011-07-27 22:23:47 -07001083
1084 private long addUriShortcut(SQLiteDatabase db, ContentValues values,
Mike Cleronb87bd162009-10-30 16:36:56 -07001085 TypedArray a) {
1086 Resources r = mContext.getResources();
1087
1088 final int iconResId = a.getResourceId(R.styleable.Favorite_icon, 0);
1089 final int titleResId = a.getResourceId(R.styleable.Favorite_title, 0);
1090
Romain Guy7eb9e5e2009-12-02 20:10:07 -08001091 Intent intent;
Mike Cleronb87bd162009-10-30 16:36:56 -07001092 String uri = null;
1093 try {
1094 uri = a.getString(R.styleable.Favorite_uri);
1095 intent = Intent.parseUri(uri, 0);
1096 } catch (URISyntaxException e) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -08001097 Log.w(TAG, "Shortcut has malformed uri: " + uri);
Adam Cohen228da5a2011-07-27 22:23:47 -07001098 return -1; // Oh well
Mike Cleronb87bd162009-10-30 16:36:56 -07001099 }
1100
1101 if (iconResId == 0 || titleResId == 0) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -08001102 Log.w(TAG, "Shortcut is missing title or icon resource ID");
Adam Cohen228da5a2011-07-27 22:23:47 -07001103 return -1;
Mike Cleronb87bd162009-10-30 16:36:56 -07001104 }
1105
Adam Cohen228da5a2011-07-27 22:23:47 -07001106 long id = generateNewId();
Mike Cleronb87bd162009-10-30 16:36:56 -07001107 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1108 values.put(Favorites.INTENT, intent.toUri(0));
1109 values.put(Favorites.TITLE, r.getString(titleResId));
1110 values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_SHORTCUT);
1111 values.put(Favorites.SPANX, 1);
1112 values.put(Favorites.SPANY, 1);
1113 values.put(Favorites.ICON_TYPE, Favorites.ICON_TYPE_RESOURCE);
1114 values.put(Favorites.ICON_PACKAGE, mContext.getPackageName());
1115 values.put(Favorites.ICON_RESOURCE, r.getResourceName(iconResId));
Adam Cohen228da5a2011-07-27 22:23:47 -07001116 values.put(Favorites._ID, id);
Mike Cleronb87bd162009-10-30 16:36:56 -07001117
Adam Cohen228da5a2011-07-27 22:23:47 -07001118 if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values) < 0) {
1119 return -1;
1120 }
1121 return id;
Mike Cleronb87bd162009-10-30 16:36:56 -07001122 }
1123 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -07001124
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001125 /**
1126 * Build a query string that will match any row where the column matches
1127 * anything in the values list.
1128 */
1129 static String buildOrWhereString(String column, int[] values) {
1130 StringBuilder selectWhere = new StringBuilder();
1131 for (int i = values.length - 1; i >= 0; i--) {
1132 selectWhere.append(column).append("=").append(values[i]);
1133 if (i > 0) {
1134 selectWhere.append(" OR ");
1135 }
1136 }
1137 return selectWhere.toString();
1138 }
1139
1140 static class SqlArguments {
1141 public final String table;
1142 public final String where;
1143 public final String[] args;
1144
1145 SqlArguments(Uri url, String where, String[] args) {
1146 if (url.getPathSegments().size() == 1) {
1147 this.table = url.getPathSegments().get(0);
1148 this.where = where;
1149 this.args = args;
1150 } else if (url.getPathSegments().size() != 2) {
1151 throw new IllegalArgumentException("Invalid URI: " + url);
1152 } else if (!TextUtils.isEmpty(where)) {
1153 throw new UnsupportedOperationException("WHERE clause not supported: " + url);
1154 } else {
1155 this.table = url.getPathSegments().get(0);
Daniel Lehmannc3a80402012-04-23 21:35:11 -07001156 this.where = "_id=" + ContentUris.parseId(url);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001157 this.args = null;
1158 }
1159 }
1160
1161 SqlArguments(Uri url) {
1162 if (url.getPathSegments().size() == 1) {
1163 table = url.getPathSegments().get(0);
1164 where = null;
1165 args = null;
1166 } else {
1167 throw new IllegalArgumentException("Invalid URI: " + url);
1168 }
1169 }
1170 }
1171}