blob: b89d632c218007f63476e99a749fc14f74574ab0 [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
Daniel Lehmannc3a80402012-04-23 21:35:11 -070019import com.android.internal.util.XmlUtils;
20import com.android.launcher.R;
21import com.android.launcher2.LauncherSettings.Favorites;
22
23import org.xmlpull.v1.XmlPullParser;
24import org.xmlpull.v1.XmlPullParserException;
25
Bjorn Bringertcd8fec02010-01-14 13:26:43 +000026import android.app.SearchManager;
The Android Open Source Project7376fae2009-03-11 12:11:58 -070027import android.appwidget.AppWidgetHost;
Mike Cleronb87bd162009-10-30 16:36:56 -070028import android.appwidget.AppWidgetManager;
Bjorn Bringertcd8fec02010-01-14 13:26:43 +000029import android.appwidget.AppWidgetProviderInfo;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080030import android.content.ComponentName;
Adam Cohen228da5a2011-07-27 22:23:47 -070031import android.content.ContentProvider;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080032import android.content.ContentResolver;
Adam Cohen228da5a2011-07-27 22:23:47 -070033import android.content.ContentUris;
34import android.content.ContentValues;
35import android.content.Context;
36import android.content.Intent;
Michael Jurkab85f8a42012-04-25 15:48:32 -070037import android.content.SharedPreferences;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080038import android.content.pm.ActivityInfo;
Adam Cohen228da5a2011-07-27 22:23:47 -070039import android.content.pm.PackageManager;
40import android.content.res.Resources;
41import android.content.res.TypedArray;
42import android.content.res.XmlResourceParser;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080043import android.database.Cursor;
44import android.database.SQLException;
Adam Cohen228da5a2011-07-27 22:23:47 -070045import android.database.sqlite.SQLiteDatabase;
46import android.database.sqlite.SQLiteOpenHelper;
47import android.database.sqlite.SQLiteQueryBuilder;
48import android.database.sqlite.SQLiteStatement;
Joe Onorato0589f0f2010-02-08 13:44:00 -080049import android.graphics.Bitmap;
50import android.graphics.BitmapFactory;
Adam Cohen228da5a2011-07-27 22:23:47 -070051import android.net.Uri;
52import android.provider.Settings;
53import android.text.TextUtils;
54import android.util.AttributeSet;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080055import android.util.Log;
56import android.util.Xml;
Adam Cohen228da5a2011-07-27 22:23:47 -070057
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
Jeffrey Sharkey2bbcae12009-03-31 14:37:57 -070078 /**
Romain Guy73b979d2009-06-09 12:57:21 -070079 * {@link Uri} triggered at any registered {@link android.database.ContentObserver} when
Jeffrey Sharkey2bbcae12009-03-31 14:37:57 -070080 * {@link AppWidgetHost#deleteHost()} is called during database creation.
81 * Use this to recall {@link AppWidgetHost#startListening()} if needed.
82 */
83 static final Uri CONTENT_APPWIDGET_RESET_URI =
84 Uri.parse("content://" + AUTHORITY + "/appWidgetReset");
Daniel Lehmannc3a80402012-04-23 21:35:11 -070085
Michael Jurkaa8c760d2011-04-28 14:59:33 -070086 private DatabaseHelper mOpenHelper;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080087
88 @Override
89 public boolean onCreate() {
90 mOpenHelper = new DatabaseHelper(getContext());
Michael Jurkaa8c760d2011-04-28 14:59:33 -070091 ((LauncherApplication) getContext()).setLauncherProvider(this);
The Android Open Source Project31dd5032009-03-03 19:32:27 -080092 return true;
93 }
94
95 @Override
96 public String getType(Uri uri) {
97 SqlArguments args = new SqlArguments(uri, null, null);
98 if (TextUtils.isEmpty(args.where)) {
99 return "vnd.android.cursor.dir/" + args.table;
100 } else {
101 return "vnd.android.cursor.item/" + args.table;
102 }
103 }
104
105 @Override
106 public Cursor query(Uri uri, String[] projection, String selection,
107 String[] selectionArgs, String sortOrder) {
108
109 SqlArguments args = new SqlArguments(uri, selection, selectionArgs);
110 SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
111 qb.setTables(args.table);
112
Romain Guy73b979d2009-06-09 12:57:21 -0700113 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800114 Cursor result = qb.query(db, projection, args.where, args.args, null, null, sortOrder);
115 result.setNotificationUri(getContext().getContentResolver(), uri);
116
117 return result;
118 }
119
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700120 private static long dbInsertAndCheck(DatabaseHelper helper,
121 SQLiteDatabase db, String table, String nullColumnHack, ContentValues values) {
122 if (!values.containsKey(LauncherSettings.Favorites._ID)) {
123 throw new RuntimeException("Error: attempting to add item without specifying an id");
124 }
125 return db.insert(table, nullColumnHack, values);
126 }
127
Adam Cohen228da5a2011-07-27 22:23:47 -0700128 private static void deleteId(SQLiteDatabase db, long id) {
129 Uri uri = LauncherSettings.Favorites.getContentUri(id, false);
130 SqlArguments args = new SqlArguments(uri, null, null);
131 db.delete(args.table, args.where, args.args);
132 }
133
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800134 @Override
135 public Uri insert(Uri uri, ContentValues initialValues) {
136 SqlArguments args = new SqlArguments(uri);
137
138 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700139 final long rowId = dbInsertAndCheck(mOpenHelper, db, args.table, null, initialValues);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800140 if (rowId <= 0) return null;
141
142 uri = ContentUris.withAppendedId(uri, rowId);
143 sendNotify(uri);
144
145 return uri;
146 }
147
148 @Override
149 public int bulkInsert(Uri uri, ContentValues[] values) {
150 SqlArguments args = new SqlArguments(uri);
151
152 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
153 db.beginTransaction();
154 try {
155 int numValues = values.length;
156 for (int i = 0; i < numValues; i++) {
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700157 if (dbInsertAndCheck(mOpenHelper, db, args.table, null, values[i]) < 0) {
158 return 0;
159 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800160 }
161 db.setTransactionSuccessful();
162 } finally {
163 db.endTransaction();
164 }
165
166 sendNotify(uri);
167 return values.length;
168 }
169
170 @Override
171 public int delete(Uri uri, String selection, String[] selectionArgs) {
172 SqlArguments args = new SqlArguments(uri, selection, selectionArgs);
173
174 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
175 int count = db.delete(args.table, args.where, args.args);
176 if (count > 0) sendNotify(uri);
177
178 return count;
179 }
180
181 @Override
182 public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
183 SqlArguments args = new SqlArguments(uri, selection, selectionArgs);
184
185 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
186 int count = db.update(args.table, values, args.where, args.args);
187 if (count > 0) sendNotify(uri);
188
189 return count;
190 }
191
192 private void sendNotify(Uri uri) {
193 String notify = uri.getQueryParameter(PARAMETER_NOTIFY);
194 if (notify == null || "true".equals(notify)) {
195 getContext().getContentResolver().notifyChange(uri, null);
196 }
197 }
198
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700199 public long generateNewId() {
200 return mOpenHelper.generateNewId();
201 }
202
Michael Jurkab85f8a42012-04-25 15:48:32 -0700203 public void loadDefaultFavoritesIfNecessary() {
204 String spKey = LauncherApplication.getSharedPreferencesKey();
205 SharedPreferences sp = getContext().getSharedPreferences(spKey, Context.MODE_PRIVATE);
206 if (sp.getBoolean(DB_CREATED_BUT_DEFAULT_WORKSPACE_NOT_LOADED, false)) {
207 // Populate favorites table with initial favorites
208 SharedPreferences.Editor editor = sp.edit();
209 editor.remove(DB_CREATED_BUT_DEFAULT_WORKSPACE_NOT_LOADED);
210 mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(), R.xml.default_workspace);
211 editor.commit();
212 }
213 }
214
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800215 private static class DatabaseHelper extends SQLiteOpenHelper {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800216 private static final String TAG_FAVORITES = "favorites";
217 private static final String TAG_FAVORITE = "favorite";
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700218 private static final String TAG_CLOCK = "clock";
219 private static final String TAG_SEARCH = "search";
Mike Cleronb87bd162009-10-30 16:36:56 -0700220 private static final String TAG_APPWIDGET = "appwidget";
221 private static final String TAG_SHORTCUT = "shortcut";
Adam Cohen228da5a2011-07-27 22:23:47 -0700222 private static final String TAG_FOLDER = "folder";
Winson Chung3d503fb2011-07-13 17:25:49 -0700223
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800224 private final Context mContext;
The Android Open Source Project7376fae2009-03-11 12:11:58 -0700225 private final AppWidgetHost mAppWidgetHost;
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700226 private long mMaxId = -1;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800227
228 DatabaseHelper(Context context) {
229 super(context, DATABASE_NAME, null, DATABASE_VERSION);
230 mContext = context;
The Android Open Source Project7376fae2009-03-11 12:11:58 -0700231 mAppWidgetHost = new AppWidgetHost(context, Launcher.APPWIDGET_HOST_ID);
Winson Chung3d503fb2011-07-13 17:25:49 -0700232
233 // In the case where neither onCreate nor onUpgrade gets called, we read the maxId from
234 // the DB here
235 if (mMaxId == -1) {
236 mMaxId = initializeMaxId(getWritableDatabase());
237 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800238 }
239
Jeffrey Sharkey2bbcae12009-03-31 14:37:57 -0700240 /**
241 * Send notification that we've deleted the {@link AppWidgetHost},
242 * probably as part of the initial database creation. The receiver may
243 * want to re-call {@link AppWidgetHost#startListening()} to ensure
244 * callbacks are correctly set.
245 */
246 private void sendAppWidgetResetNotify() {
247 final ContentResolver resolver = mContext.getContentResolver();
248 resolver.notifyChange(CONTENT_APPWIDGET_RESET_URI, null);
249 }
250
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800251 @Override
252 public void onCreate(SQLiteDatabase db) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800253 if (LOGD) Log.d(TAG, "creating new launcher database");
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700254
255 mMaxId = 1;
256
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800257 db.execSQL("CREATE TABLE favorites (" +
258 "_id INTEGER PRIMARY KEY," +
259 "title TEXT," +
260 "intent TEXT," +
261 "container INTEGER," +
262 "screen INTEGER," +
263 "cellX INTEGER," +
264 "cellY INTEGER," +
265 "spanX INTEGER," +
266 "spanY INTEGER," +
267 "itemType INTEGER," +
The Android Open Source Projectca9475f2009-03-13 13:04:24 -0700268 "appWidgetId INTEGER NOT NULL DEFAULT -1," +
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800269 "isShortcut INTEGER," +
270 "iconType INTEGER," +
271 "iconPackage TEXT," +
272 "iconResource TEXT," +
273 "icon BLOB," +
274 "uri TEXT," +
275 "displayMode INTEGER" +
276 ");");
277
The Android Open Source Project7376fae2009-03-11 12:11:58 -0700278 // Database was just created, so wipe any previous widgets
279 if (mAppWidgetHost != null) {
280 mAppWidgetHost.deleteHost();
Jeffrey Sharkey2bbcae12009-03-31 14:37:57 -0700281 sendAppWidgetResetNotify();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800282 }
Winson Chung3d503fb2011-07-13 17:25:49 -0700283
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800284 if (!convertDatabase(db)) {
Michael Jurkab85f8a42012-04-25 15:48:32 -0700285 // Set a shared pref so that we know we need to load the default workspace later
286 setFlagToLoadDefaultWorkspaceLater();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800287 }
288 }
289
Michael Jurkab85f8a42012-04-25 15:48:32 -0700290 private void setFlagToLoadDefaultWorkspaceLater() {
291 String spKey = LauncherApplication.getSharedPreferencesKey();
292 SharedPreferences sp = mContext.getSharedPreferences(spKey, Context.MODE_PRIVATE);
293 SharedPreferences.Editor editor = sp.edit();
294 editor.putBoolean(DB_CREATED_BUT_DEFAULT_WORKSPACE_NOT_LOADED, true);
295 editor.commit();
296 }
297
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800298 private boolean convertDatabase(SQLiteDatabase db) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800299 if (LOGD) Log.d(TAG, "converting database from an older format, but not onUpgrade");
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800300 boolean converted = false;
301
302 final Uri uri = Uri.parse("content://" + Settings.AUTHORITY +
303 "/old_favorites?notify=true");
304 final ContentResolver resolver = mContext.getContentResolver();
305 Cursor cursor = null;
306
307 try {
308 cursor = resolver.query(uri, null, null, null, null);
309 } catch (Exception e) {
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700310 // Ignore
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800311 }
312
313 // We already have a favorites database in the old provider
314 if (cursor != null && cursor.getCount() > 0) {
315 try {
316 converted = copyFromCursor(db, cursor) > 0;
317 } finally {
318 cursor.close();
319 }
320
321 if (converted) {
322 resolver.delete(uri, null, null);
323 }
324 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700325
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800326 if (converted) {
The Android Open Source Project7376fae2009-03-11 12:11:58 -0700327 // Convert widgets from this import into widgets
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800328 if (LOGD) Log.d(TAG, "converted and now triggering widget upgrade");
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800329 convertWidgets(db);
330 }
331
332 return converted;
333 }
334
335 private int copyFromCursor(SQLiteDatabase db, Cursor c) {
Romain Guy73b979d2009-06-09 12:57:21 -0700336 final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800337 final int intentIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.INTENT);
338 final int titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE);
339 final int iconTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_TYPE);
340 final int iconIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON);
341 final int iconPackageIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_PACKAGE);
342 final int iconResourceIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_RESOURCE);
343 final int containerIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER);
344 final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE);
345 final int screenIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN);
346 final int cellXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX);
347 final int cellYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY);
348 final int uriIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.URI);
349 final int displayModeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.DISPLAY_MODE);
350
351 ContentValues[] rows = new ContentValues[c.getCount()];
352 int i = 0;
353 while (c.moveToNext()) {
354 ContentValues values = new ContentValues(c.getColumnCount());
Romain Guy73b979d2009-06-09 12:57:21 -0700355 values.put(LauncherSettings.Favorites._ID, c.getLong(idIndex));
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800356 values.put(LauncherSettings.Favorites.INTENT, c.getString(intentIndex));
357 values.put(LauncherSettings.Favorites.TITLE, c.getString(titleIndex));
358 values.put(LauncherSettings.Favorites.ICON_TYPE, c.getInt(iconTypeIndex));
359 values.put(LauncherSettings.Favorites.ICON, c.getBlob(iconIndex));
360 values.put(LauncherSettings.Favorites.ICON_PACKAGE, c.getString(iconPackageIndex));
361 values.put(LauncherSettings.Favorites.ICON_RESOURCE, c.getString(iconResourceIndex));
362 values.put(LauncherSettings.Favorites.CONTAINER, c.getInt(containerIndex));
363 values.put(LauncherSettings.Favorites.ITEM_TYPE, c.getInt(itemTypeIndex));
The Android Open Source Project7376fae2009-03-11 12:11:58 -0700364 values.put(LauncherSettings.Favorites.APPWIDGET_ID, -1);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800365 values.put(LauncherSettings.Favorites.SCREEN, c.getInt(screenIndex));
366 values.put(LauncherSettings.Favorites.CELLX, c.getInt(cellXIndex));
367 values.put(LauncherSettings.Favorites.CELLY, c.getInt(cellYIndex));
368 values.put(LauncherSettings.Favorites.URI, c.getString(uriIndex));
369 values.put(LauncherSettings.Favorites.DISPLAY_MODE, c.getInt(displayModeIndex));
370 rows[i++] = values;
371 }
372
373 db.beginTransaction();
374 int total = 0;
375 try {
376 int numValues = rows.length;
377 for (i = 0; i < numValues; i++) {
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700378 if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, rows[i]) < 0) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800379 return 0;
380 } else {
381 total++;
382 }
383 }
384 db.setTransactionSuccessful();
385 } finally {
386 db.endTransaction();
387 }
388
389 return total;
390 }
391
392 @Override
393 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800394 if (LOGD) Log.d(TAG, "onUpgrade triggered");
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700395
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800396 int version = oldVersion;
The Android Open Source Projectca9475f2009-03-13 13:04:24 -0700397 if (version < 3) {
398 // upgrade 1,2 -> 3 added appWidgetId column
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800399 db.beginTransaction();
400 try {
The Android Open Source Project7376fae2009-03-11 12:11:58 -0700401 // Insert new column for holding appWidgetIds
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800402 db.execSQL("ALTER TABLE favorites " +
The Android Open Source Projectca9475f2009-03-13 13:04:24 -0700403 "ADD COLUMN appWidgetId INTEGER NOT NULL DEFAULT -1;");
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800404 db.setTransactionSuccessful();
The Android Open Source Projectca9475f2009-03-13 13:04:24 -0700405 version = 3;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800406 } catch (SQLException ex) {
407 // Old version remains, which means we wipe old data
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800408 Log.e(TAG, ex.getMessage(), ex);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800409 } finally {
410 db.endTransaction();
411 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700412
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800413 // Convert existing widgets only if table upgrade was successful
The Android Open Source Projectca9475f2009-03-13 13:04:24 -0700414 if (version == 3) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800415 convertWidgets(db);
416 }
417 }
Romain Guy73b979d2009-06-09 12:57:21 -0700418
419 if (version < 4) {
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800420 version = 4;
Romain Guy73b979d2009-06-09 12:57:21 -0700421 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700422
Romain Guy509cd6a2010-03-23 15:10:56 -0700423 // Where's version 5?
424 // - Donut and sholes on 2.0 shipped with version 4 of launcher1.
425 // - Passion shipped on 2.1 with version 6 of launcher2
426 // - Sholes shipped on 2.1r1 (aka Mr. 3) with version 5 of launcher 1
427 // but version 5 on there was the updateContactsShortcuts change
428 // which was version 6 in launcher 2 (first shipped on passion 2.1r1).
429 // The updateContactsShortcuts change is idempotent, so running it twice
430 // is okay so we'll do that when upgrading the devices that shipped with it.
431 if (version < 6) {
Mike Cleron3a2b3f22009-11-05 17:17:42 -0800432 // We went from 3 to 5 screens. Move everything 1 to the right
433 db.beginTransaction();
434 try {
435 db.execSQL("UPDATE favorites SET screen=(screen + 1);");
436 db.setTransactionSuccessful();
Mike Cleron3a2b3f22009-11-05 17:17:42 -0800437 } catch (SQLException ex) {
438 // Old version remains, which means we wipe old data
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800439 Log.e(TAG, ex.getMessage(), ex);
Mike Cleron3a2b3f22009-11-05 17:17:42 -0800440 } finally {
441 db.endTransaction();
442 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700443
Romain Guy509cd6a2010-03-23 15:10:56 -0700444 // We added the fast track.
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800445 if (updateContactsShortcuts(db)) {
446 version = 6;
447 }
448 }
Bjorn Bringert7984c942009-12-09 15:38:25 +0000449
450 if (version < 7) {
451 // Version 7 gets rid of the special search widget.
452 convertWidgets(db);
453 version = 7;
454 }
455
Joe Onorato0589f0f2010-02-08 13:44:00 -0800456 if (version < 8) {
457 // Version 8 (froyo) has the icons all normalized. This should
458 // already be the case in practice, but we now rely on it and don't
459 // resample the images each time.
460 normalizeIcons(db);
461 version = 8;
462 }
463
Winson Chung3d503fb2011-07-13 17:25:49 -0700464 if (version < 9) {
465 // The max id is not yet set at this point (onUpgrade is triggered in the ctor
466 // before it gets a change to get set, so we need to read it here when we use it)
467 if (mMaxId == -1) {
468 mMaxId = initializeMaxId(db);
469 }
470
471 // Add default hotseat icons
Winson Chung6d092682011-11-16 18:43:26 -0800472 loadFavorites(db, R.xml.update_workspace);
Winson Chung3d503fb2011-07-13 17:25:49 -0700473 version = 9;
474 }
475
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700476 if (version < 10) {
477 // Contact shortcuts need a different set of flags to be launched now
478 // The updateContactsShortcuts change is idempotent, so we can keep using it like
479 // back in the Donut days
480 updateContactsShortcuts(db);
481 version = 10;
482 }
483
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800484 if (version != DATABASE_VERSION) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800485 Log.w(TAG, "Destroying all old data.");
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800486 db.execSQL("DROP TABLE IF EXISTS " + TABLE_FAVORITES);
487 onCreate(db);
488 }
489 }
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800490
491 private boolean updateContactsShortcuts(SQLiteDatabase db) {
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800492 final String selectWhere = buildOrWhereString(Favorites.ITEM_TYPE,
493 new int[] { Favorites.ITEM_TYPE_SHORTCUT });
494
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700495 Cursor c = null;
496 final String actionQuickContact = "com.android.contacts.action.QUICK_CONTACT";
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800497 db.beginTransaction();
498 try {
499 // Select and iterate through each matching widget
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700500 c = db.query(TABLE_FAVORITES,
501 new String[] { Favorites._ID, Favorites.INTENT },
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800502 selectWhere, null, null, null, null);
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700503 if (c == null) return false;
504
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800505 if (LOGD) Log.d(TAG, "found upgrade cursor count=" + c.getCount());
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700506
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800507 final int idIndex = c.getColumnIndex(Favorites._ID);
508 final int intentIndex = c.getColumnIndex(Favorites.INTENT);
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700509
510 while (c.moveToNext()) {
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800511 long favoriteId = c.getLong(idIndex);
512 final String intentUri = c.getString(intentIndex);
513 if (intentUri != null) {
514 try {
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700515 final Intent intent = Intent.parseUri(intentUri, 0);
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800516 android.util.Log.d("Home", intent.toString());
517 final Uri uri = intent.getData();
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700518 if (uri != null) {
519 final String data = uri.toString();
520 if ((Intent.ACTION_VIEW.equals(intent.getAction()) ||
521 actionQuickContact.equals(intent.getAction())) &&
522 (data.startsWith("content://contacts/people/") ||
523 data.startsWith("content://com.android.contacts/" +
524 "contacts/lookup/"))) {
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800525
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700526 final Intent newIntent = new Intent(actionQuickContact);
527 // When starting from the launcher, start in a new, cleared task
528 // CLEAR_WHEN_TASK_RESET cannot reset the root of a task, so we
529 // clear the whole thing preemptively here since
530 // QuickContactActivity will finish itself when launching other
531 // detail activities.
532 newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
533 Intent.FLAG_ACTIVITY_CLEAR_TASK);
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800534
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700535 newIntent.setData(uri);
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800536
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700537 final ContentValues values = new ContentValues();
538 values.put(LauncherSettings.Favorites.INTENT,
539 newIntent.toUri(0));
540
541 String updateWhere = Favorites._ID + "=" + favoriteId;
542 db.update(TABLE_FAVORITES, values, updateWhere, null);
543 }
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800544 }
545 } catch (RuntimeException ex) {
546 Log.e(TAG, "Problem upgrading shortcut", ex);
547 } catch (URISyntaxException e) {
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700548 Log.e(TAG, "Problem upgrading shortcut", e);
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800549 }
550 }
551 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700552
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800553 db.setTransactionSuccessful();
554 } catch (SQLException ex) {
555 Log.w(TAG, "Problem while upgrading contacts", ex);
556 return false;
557 } finally {
558 db.endTransaction();
559 if (c != null) {
560 c.close();
561 }
562 }
563
564 return true;
565 }
566
Joe Onorato0589f0f2010-02-08 13:44:00 -0800567 private void normalizeIcons(SQLiteDatabase db) {
568 Log.d(TAG, "normalizing icons");
569
Joe Onorato346e1292010-02-18 10:34:24 -0500570 db.beginTransaction();
Joe Onorato0589f0f2010-02-08 13:44:00 -0800571 Cursor c = null;
Joe Onorato9690b392010-03-23 17:34:37 -0400572 SQLiteStatement update = null;
Joe Onorato0589f0f2010-02-08 13:44:00 -0800573 try {
574 boolean logged = false;
Joe Onorato9690b392010-03-23 17:34:37 -0400575 update = db.compileStatement("UPDATE favorites "
Jeff Hamiltoneaf77d62010-02-13 00:08:17 -0600576 + "SET icon=? WHERE _id=?");
Joe Onorato0589f0f2010-02-08 13:44:00 -0800577
578 c = db.rawQuery("SELECT _id, icon FROM favorites WHERE iconType=" +
579 Favorites.ICON_TYPE_BITMAP, null);
580
581 final int idIndex = c.getColumnIndexOrThrow(Favorites._ID);
582 final int iconIndex = c.getColumnIndexOrThrow(Favorites.ICON);
583
584 while (c.moveToNext()) {
585 long id = c.getLong(idIndex);
586 byte[] data = c.getBlob(iconIndex);
587 try {
588 Bitmap bitmap = Utilities.resampleIconBitmap(
589 BitmapFactory.decodeByteArray(data, 0, data.length),
590 mContext);
591 if (bitmap != null) {
592 update.bindLong(1, id);
593 data = ItemInfo.flattenBitmap(bitmap);
594 if (data != null) {
595 update.bindBlob(2, data);
596 update.execute();
597 }
598 bitmap.recycle();
Joe Onorato0589f0f2010-02-08 13:44:00 -0800599 }
600 } catch (Exception e) {
601 if (!logged) {
602 Log.e(TAG, "Failed normalizing icon " + id, e);
603 } else {
604 Log.e(TAG, "Also failed normalizing icon " + id);
605 }
606 logged = true;
607 }
608 }
Bjorn Bringert3a928e42010-02-19 11:15:40 +0000609 db.setTransactionSuccessful();
Joe Onorato0589f0f2010-02-08 13:44:00 -0800610 } catch (SQLException ex) {
611 Log.w(TAG, "Problem while allocating appWidgetIds for existing widgets", ex);
612 } finally {
613 db.endTransaction();
Joe Onorato9690b392010-03-23 17:34:37 -0400614 if (update != null) {
615 update.close();
616 }
Joe Onorato0589f0f2010-02-08 13:44:00 -0800617 if (c != null) {
618 c.close();
619 }
620 }
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700621 }
622
623 // Generates a new ID to use for an object in your database. This method should be only
624 // called from the main UI thread. As an exception, we do call it when we call the
625 // constructor from the worker thread; however, this doesn't extend until after the
626 // constructor is called, and we only pass a reference to LauncherProvider to LauncherApp
627 // after that point
628 public long generateNewId() {
629 if (mMaxId < 0) {
630 throw new RuntimeException("Error: max id was not initialized");
631 }
632 mMaxId += 1;
633 return mMaxId;
634 }
635
636 private long initializeMaxId(SQLiteDatabase db) {
637 Cursor c = db.rawQuery("SELECT MAX(_id) FROM favorites", null);
638
639 // get the result
640 final int maxIdIndex = 0;
641 long id = -1;
642 if (c != null && c.moveToNext()) {
643 id = c.getLong(maxIdIndex);
644 }
Michael Jurka5130e402011-10-13 04:55:35 -0700645 if (c != null) {
646 c.close();
647 }
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700648
649 if (id == -1) {
650 throw new RuntimeException("Error: could not query max id");
651 }
652
653 return id;
Joe Onorato0589f0f2010-02-08 13:44:00 -0800654 }
655
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800656 /**
The Android Open Source Project7376fae2009-03-11 12:11:58 -0700657 * Upgrade existing clock and photo frame widgets into their new widget
Bjorn Bringert93c45762009-12-16 13:19:47 +0000658 * equivalents.
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800659 */
660 private void convertWidgets(SQLiteDatabase db) {
Bjorn Bringert34251342009-12-15 13:33:11 +0000661 final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800662 final int[] bindSources = new int[] {
663 Favorites.ITEM_TYPE_WIDGET_CLOCK,
664 Favorites.ITEM_TYPE_WIDGET_PHOTO_FRAME,
Bjorn Bringert7984c942009-12-09 15:38:25 +0000665 Favorites.ITEM_TYPE_WIDGET_SEARCH,
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800666 };
Bjorn Bringert7984c942009-12-09 15:38:25 +0000667
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800668 final String selectWhere = buildOrWhereString(Favorites.ITEM_TYPE, bindSources);
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700669
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800670 Cursor c = null;
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700671
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800672 db.beginTransaction();
673 try {
674 // Select and iterate through each matching widget
Bjorn Bringert7984c942009-12-09 15:38:25 +0000675 c = db.query(TABLE_FAVORITES, new String[] { Favorites._ID, Favorites.ITEM_TYPE },
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800676 selectWhere, null, null, null, null);
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700677
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800678 if (LOGD) Log.d(TAG, "found upgrade cursor count=" + c.getCount());
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700679
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800680 final ContentValues values = new ContentValues();
681 while (c != null && c.moveToNext()) {
682 long favoriteId = c.getLong(0);
Bjorn Bringert7984c942009-12-09 15:38:25 +0000683 int favoriteType = c.getInt(1);
684
The Android Open Source Project7376fae2009-03-11 12:11:58 -0700685 // Allocate and update database with new appWidgetId
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800686 try {
The Android Open Source Project7376fae2009-03-11 12:11:58 -0700687 int appWidgetId = mAppWidgetHost.allocateAppWidgetId();
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700688
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800689 if (LOGD) {
690 Log.d(TAG, "allocated appWidgetId=" + appWidgetId
691 + " for favoriteId=" + favoriteId);
692 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800693 values.clear();
Bjorn Bringert7984c942009-12-09 15:38:25 +0000694 values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_APPWIDGET);
695 values.put(Favorites.APPWIDGET_ID, appWidgetId);
696
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800697 // Original widgets might not have valid spans when upgrading
Bjorn Bringert7984c942009-12-09 15:38:25 +0000698 if (favoriteType == Favorites.ITEM_TYPE_WIDGET_SEARCH) {
699 values.put(LauncherSettings.Favorites.SPANX, 4);
700 values.put(LauncherSettings.Favorites.SPANY, 1);
701 } else {
702 values.put(LauncherSettings.Favorites.SPANX, 2);
703 values.put(LauncherSettings.Favorites.SPANY, 2);
704 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800705
706 String updateWhere = Favorites._ID + "=" + favoriteId;
707 db.update(TABLE_FAVORITES, values, updateWhere, null);
Bjorn Bringert34251342009-12-15 13:33:11 +0000708
Bjorn Bringert34251342009-12-15 13:33:11 +0000709 if (favoriteType == Favorites.ITEM_TYPE_WIDGET_CLOCK) {
710 appWidgetManager.bindAppWidgetId(appWidgetId,
711 new ComponentName("com.android.alarmclock",
712 "com.android.alarmclock.AnalogAppWidgetProvider"));
713 } else if (favoriteType == Favorites.ITEM_TYPE_WIDGET_PHOTO_FRAME) {
714 appWidgetManager.bindAppWidgetId(appWidgetId,
715 new ComponentName("com.android.camera",
716 "com.android.camera.PhotoAppWidgetProvider"));
717 } else if (favoriteType == Favorites.ITEM_TYPE_WIDGET_SEARCH) {
718 appWidgetManager.bindAppWidgetId(appWidgetId,
Bjorn Bringertcd8fec02010-01-14 13:26:43 +0000719 getSearchWidgetProvider());
Bjorn Bringert34251342009-12-15 13:33:11 +0000720 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800721 } catch (RuntimeException ex) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800722 Log.e(TAG, "Problem allocating appWidgetId", ex);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800723 }
724 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700725
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800726 db.setTransactionSuccessful();
727 } catch (SQLException ex) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800728 Log.w(TAG, "Problem while allocating appWidgetIds for existing widgets", ex);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800729 } finally {
730 db.endTransaction();
731 if (c != null) {
732 c.close();
733 }
734 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800735 }
736
737 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800738 * Loads the default set of favorite packages from an xml file.
739 *
740 * @param db The database to write the values into
Winson Chung3d503fb2011-07-13 17:25:49 -0700741 * @param filterContainerId The specific container id of items to load
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800742 */
Winson Chung6d092682011-11-16 18:43:26 -0800743 private int loadFavorites(SQLiteDatabase db, int workspaceResourceId) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800744 Intent intent = new Intent(Intent.ACTION_MAIN, null);
745 intent.addCategory(Intent.CATEGORY_LAUNCHER);
746 ContentValues values = new ContentValues();
747
748 PackageManager packageManager = mContext.getPackageManager();
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800749 int allAppsButtonRank =
750 mContext.getResources().getInteger(R.integer.hotseat_all_apps_index);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800751 int i = 0;
752 try {
Winson Chung6d092682011-11-16 18:43:26 -0800753 XmlResourceParser parser = mContext.getResources().getXml(workspaceResourceId);
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700754 AttributeSet attrs = Xml.asAttributeSet(parser);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800755 XmlUtils.beginDocument(parser, TAG_FAVORITES);
756
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700757 final int depth = parser.getDepth();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800758
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700759 int type;
760 while (((type = parser.next()) != XmlPullParser.END_TAG ||
761 parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
762
763 if (type != XmlPullParser.START_TAG) {
764 continue;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800765 }
766
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700767 boolean added = false;
768 final String name = parser.getName();
769
770 TypedArray a = mContext.obtainStyledAttributes(attrs, R.styleable.Favorite);
771
Winson Chung3d503fb2011-07-13 17:25:49 -0700772 long container = LauncherSettings.Favorites.CONTAINER_DESKTOP;
773 if (a.hasValue(R.styleable.Favorite_container)) {
774 container = Long.valueOf(a.getString(R.styleable.Favorite_container));
775 }
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700776
Winson Chung6d092682011-11-16 18:43:26 -0800777 String screen = a.getString(R.styleable.Favorite_screen);
778 String x = a.getString(R.styleable.Favorite_x);
779 String y = a.getString(R.styleable.Favorite_y);
780
781 // If we are adding to the hotseat, the screen is used as the position in the
782 // hotseat. This screen can't be at position 0 because AllApps is in the
783 // zeroth position.
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800784 if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT
785 && Integer.valueOf(screen) == allAppsButtonRank) {
Winson Chung6d092682011-11-16 18:43:26 -0800786 throw new RuntimeException("Invalid screen position for hotseat item");
787 }
788
789 values.clear();
790 values.put(LauncherSettings.Favorites.CONTAINER, container);
791 values.put(LauncherSettings.Favorites.SCREEN, screen);
792 values.put(LauncherSettings.Favorites.CELLX, x);
793 values.put(LauncherSettings.Favorites.CELLY, y);
794
795 if (TAG_FAVORITE.equals(name)) {
796 long id = addAppShortcut(db, values, a, packageManager, intent);
797 added = id >= 0;
798 } else if (TAG_SEARCH.equals(name)) {
799 added = addSearchWidget(db, values);
800 } else if (TAG_CLOCK.equals(name)) {
801 added = addClockWidget(db, values);
802 } else if (TAG_APPWIDGET.equals(name)) {
803 added = addAppWidget(db, values, a, packageManager);
804 } else if (TAG_SHORTCUT.equals(name)) {
805 long id = addUriShortcut(db, values, a);
806 added = id >= 0;
807 } else if (TAG_FOLDER.equals(name)) {
808 String title;
809 int titleResId = a.getResourceId(R.styleable.Favorite_title, -1);
810 if (titleResId != -1) {
811 title = mContext.getResources().getString(titleResId);
812 } else {
813 title = mContext.getResources().getString(R.string.folder_name);
Winson Chung3d503fb2011-07-13 17:25:49 -0700814 }
Winson Chung6d092682011-11-16 18:43:26 -0800815 values.put(LauncherSettings.Favorites.TITLE, title);
816 long folderId = addFolder(db, values);
817 added = folderId >= 0;
Winson Chung3d503fb2011-07-13 17:25:49 -0700818
Winson Chung6d092682011-11-16 18:43:26 -0800819 ArrayList<Long> folderItems = new ArrayList<Long>();
Winson Chung3d503fb2011-07-13 17:25:49 -0700820
Winson Chung6d092682011-11-16 18:43:26 -0800821 int folderDepth = parser.getDepth();
822 while ((type = parser.next()) != XmlPullParser.END_TAG ||
823 parser.getDepth() > folderDepth) {
824 if (type != XmlPullParser.START_TAG) {
825 continue;
826 }
827 final String folder_item_name = parser.getName();
828
829 TypedArray ar = mContext.obtainStyledAttributes(attrs,
830 R.styleable.Favorite);
831 values.clear();
832 values.put(LauncherSettings.Favorites.CONTAINER, folderId);
833
834 if (TAG_FAVORITE.equals(folder_item_name) && folderId >= 0) {
835 long id =
836 addAppShortcut(db, values, ar, packageManager, intent);
837 if (id >= 0) {
838 folderItems.add(id);
839 }
840 } else if (TAG_SHORTCUT.equals(folder_item_name) && folderId >= 0) {
841 long id = addUriShortcut(db, values, ar);
842 if (id >= 0) {
843 folderItems.add(id);
844 }
Adam Cohen228da5a2011-07-27 22:23:47 -0700845 } else {
Winson Chung6d092682011-11-16 18:43:26 -0800846 throw new RuntimeException("Folders can " +
847 "contain only shortcuts");
Adam Cohen228da5a2011-07-27 22:23:47 -0700848 }
Winson Chung6d092682011-11-16 18:43:26 -0800849 ar.recycle();
850 }
851 // We can only have folders with >= 2 items, so we need to remove the
852 // folder and clean up if less than 2 items were included, or some
853 // failed to add, and less than 2 were actually added
854 if (folderItems.size() < 2 && folderId >= 0) {
855 // We just delete the folder and any items that made it
856 deleteId(db, folderId);
857 if (folderItems.size() > 0) {
858 deleteId(db, folderItems.get(0));
Adam Cohen228da5a2011-07-27 22:23:47 -0700859 }
Winson Chung6d092682011-11-16 18:43:26 -0800860 added = false;
Winson Chung3d503fb2011-07-13 17:25:49 -0700861 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800862 }
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700863 if (added) i++;
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700864 a.recycle();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800865 }
866 } catch (XmlPullParserException e) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800867 Log.w(TAG, "Got exception parsing favorites.", e);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800868 } catch (IOException e) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800869 Log.w(TAG, "Got exception parsing favorites.", e);
Winson Chung3d503fb2011-07-13 17:25:49 -0700870 } catch (RuntimeException e) {
871 Log.w(TAG, "Got exception parsing favorites.", e);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800872 }
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700873
874 return i;
875 }
876
Adam Cohen228da5a2011-07-27 22:23:47 -0700877 private long addAppShortcut(SQLiteDatabase db, ContentValues values, TypedArray a,
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700878 PackageManager packageManager, Intent intent) {
Adam Cohen228da5a2011-07-27 22:23:47 -0700879 long id = -1;
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700880 ActivityInfo info;
881 String packageName = a.getString(R.styleable.Favorite_packageName);
882 String className = a.getString(R.styleable.Favorite_className);
883 try {
Romain Guy693599f2010-03-23 10:58:18 -0700884 ComponentName cn;
885 try {
886 cn = new ComponentName(packageName, className);
887 info = packageManager.getActivityInfo(cn, 0);
888 } catch (PackageManager.NameNotFoundException nnfe) {
889 String[] packages = packageManager.currentToCanonicalPackageNames(
890 new String[] { packageName });
891 cn = new ComponentName(packages[0], className);
892 info = packageManager.getActivityInfo(cn, 0);
893 }
Adam Cohen228da5a2011-07-27 22:23:47 -0700894 id = generateNewId();
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700895 intent.setComponent(cn);
Romain Guy693599f2010-03-23 10:58:18 -0700896 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
897 Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
Romain Guy1ce1a242009-06-23 17:34:54 -0700898 values.put(Favorites.INTENT, intent.toUri(0));
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700899 values.put(Favorites.TITLE, info.loadLabel(packageManager).toString());
900 values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_APPLICATION);
901 values.put(Favorites.SPANX, 1);
902 values.put(Favorites.SPANY, 1);
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700903 values.put(Favorites._ID, generateNewId());
Adam Cohen228da5a2011-07-27 22:23:47 -0700904 if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values) < 0) {
905 return -1;
906 }
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700907 } catch (PackageManager.NameNotFoundException e) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800908 Log.w(TAG, "Unable to add favorite: " + packageName +
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700909 "/" + className, e);
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700910 }
Adam Cohen228da5a2011-07-27 22:23:47 -0700911 return id;
912 }
913
914 private long addFolder(SQLiteDatabase db, ContentValues values) {
915 values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_FOLDER);
916 values.put(Favorites.SPANX, 1);
917 values.put(Favorites.SPANY, 1);
918 long id = generateNewId();
919 values.put(Favorites._ID, id);
920 if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values) <= 0) {
921 return -1;
922 } else {
923 return id;
924 }
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700925 }
926
Bjorn Bringertcd8fec02010-01-14 13:26:43 +0000927 private ComponentName getSearchWidgetProvider() {
928 SearchManager searchManager =
929 (SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE);
930 ComponentName searchComponent = searchManager.getGlobalSearchActivity();
931 if (searchComponent == null) return null;
932 return getProviderInPackage(searchComponent.getPackageName());
933 }
934
935 /**
936 * Gets an appwidget provider from the given package. If the package contains more than
937 * one appwidget provider, an arbitrary one is returned.
938 */
939 private ComponentName getProviderInPackage(String packageName) {
940 AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext);
941 List<AppWidgetProviderInfo> providers = appWidgetManager.getInstalledProviders();
942 if (providers == null) return null;
943 final int providerCount = providers.size();
944 for (int i = 0; i < providerCount; i++) {
945 ComponentName provider = providers.get(i).provider;
946 if (provider != null && provider.getPackageName().equals(packageName)) {
947 return provider;
948 }
949 }
950 return null;
951 }
952
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700953 private boolean addSearchWidget(SQLiteDatabase db, ContentValues values) {
Bjorn Bringertcd8fec02010-01-14 13:26:43 +0000954 ComponentName cn = getSearchWidgetProvider();
Bjorn Bringert7984c942009-12-09 15:38:25 +0000955 return addAppWidget(db, values, cn, 4, 1);
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700956 }
957
958 private boolean addClockWidget(SQLiteDatabase db, ContentValues values) {
Bjorn Bringert34251342009-12-15 13:33:11 +0000959 ComponentName cn = new ComponentName("com.android.alarmclock",
960 "com.android.alarmclock.AnalogAppWidgetProvider");
961 return addAppWidget(db, values, cn, 2, 2);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800962 }
Adam Cohen228da5a2011-07-27 22:23:47 -0700963
Romain Guy693599f2010-03-23 10:58:18 -0700964 private boolean addAppWidget(SQLiteDatabase db, ContentValues values, TypedArray a,
965 PackageManager packageManager) {
966
Mike Cleronb87bd162009-10-30 16:36:56 -0700967 String packageName = a.getString(R.styleable.Favorite_packageName);
968 String className = a.getString(R.styleable.Favorite_className);
969
970 if (packageName == null || className == null) {
971 return false;
972 }
Romain Guy693599f2010-03-23 10:58:18 -0700973
974 boolean hasPackage = true;
Mike Cleronb87bd162009-10-30 16:36:56 -0700975 ComponentName cn = new ComponentName(packageName, className);
Romain Guy693599f2010-03-23 10:58:18 -0700976 try {
977 packageManager.getReceiverInfo(cn, 0);
978 } catch (Exception e) {
979 String[] packages = packageManager.currentToCanonicalPackageNames(
980 new String[] { packageName });
981 cn = new ComponentName(packages[0], className);
982 try {
983 packageManager.getReceiverInfo(cn, 0);
984 } catch (Exception e1) {
985 hasPackage = false;
986 }
987 }
988
989 if (hasPackage) {
990 int spanX = a.getInt(R.styleable.Favorite_spanX, 0);
991 int spanY = a.getInt(R.styleable.Favorite_spanY, 0);
992 return addAppWidget(db, values, cn, spanX, spanY);
993 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700994
Romain Guy693599f2010-03-23 10:58:18 -0700995 return false;
Bjorn Bringert7984c942009-12-09 15:38:25 +0000996 }
997
998 private boolean addAppWidget(SQLiteDatabase db, ContentValues values, ComponentName cn,
999 int spanX, int spanY) {
Mike Cleronb87bd162009-10-30 16:36:56 -07001000 boolean allocatedAppWidgets = false;
1001 final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext);
1002
1003 try {
1004 int appWidgetId = mAppWidgetHost.allocateAppWidgetId();
Daniel Lehmannc3a80402012-04-23 21:35:11 -07001005
Mike Cleronb87bd162009-10-30 16:36:56 -07001006 values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_APPWIDGET);
Bjorn Bringert7984c942009-12-09 15:38:25 +00001007 values.put(Favorites.SPANX, spanX);
1008 values.put(Favorites.SPANY, spanY);
Mike Cleronb87bd162009-10-30 16:36:56 -07001009 values.put(Favorites.APPWIDGET_ID, appWidgetId);
Michael Jurkaa8c760d2011-04-28 14:59:33 -07001010 values.put(Favorites._ID, generateNewId());
1011 dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values);
Mike Cleronb87bd162009-10-30 16:36:56 -07001012
1013 allocatedAppWidgets = true;
Daniel Lehmannc3a80402012-04-23 21:35:11 -07001014
Mike Cleronb87bd162009-10-30 16:36:56 -07001015 appWidgetManager.bindAppWidgetId(appWidgetId, cn);
1016 } catch (RuntimeException ex) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -08001017 Log.e(TAG, "Problem allocating appWidgetId", ex);
Mike Cleronb87bd162009-10-30 16:36:56 -07001018 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -07001019
Mike Cleronb87bd162009-10-30 16:36:56 -07001020 return allocatedAppWidgets;
1021 }
Adam Cohen228da5a2011-07-27 22:23:47 -07001022
1023 private long addUriShortcut(SQLiteDatabase db, ContentValues values,
Mike Cleronb87bd162009-10-30 16:36:56 -07001024 TypedArray a) {
1025 Resources r = mContext.getResources();
1026
1027 final int iconResId = a.getResourceId(R.styleable.Favorite_icon, 0);
1028 final int titleResId = a.getResourceId(R.styleable.Favorite_title, 0);
1029
Romain Guy7eb9e5e2009-12-02 20:10:07 -08001030 Intent intent;
Mike Cleronb87bd162009-10-30 16:36:56 -07001031 String uri = null;
1032 try {
1033 uri = a.getString(R.styleable.Favorite_uri);
1034 intent = Intent.parseUri(uri, 0);
1035 } catch (URISyntaxException e) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -08001036 Log.w(TAG, "Shortcut has malformed uri: " + uri);
Adam Cohen228da5a2011-07-27 22:23:47 -07001037 return -1; // Oh well
Mike Cleronb87bd162009-10-30 16:36:56 -07001038 }
1039
1040 if (iconResId == 0 || titleResId == 0) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -08001041 Log.w(TAG, "Shortcut is missing title or icon resource ID");
Adam Cohen228da5a2011-07-27 22:23:47 -07001042 return -1;
Mike Cleronb87bd162009-10-30 16:36:56 -07001043 }
1044
Adam Cohen228da5a2011-07-27 22:23:47 -07001045 long id = generateNewId();
Mike Cleronb87bd162009-10-30 16:36:56 -07001046 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1047 values.put(Favorites.INTENT, intent.toUri(0));
1048 values.put(Favorites.TITLE, r.getString(titleResId));
1049 values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_SHORTCUT);
1050 values.put(Favorites.SPANX, 1);
1051 values.put(Favorites.SPANY, 1);
1052 values.put(Favorites.ICON_TYPE, Favorites.ICON_TYPE_RESOURCE);
1053 values.put(Favorites.ICON_PACKAGE, mContext.getPackageName());
1054 values.put(Favorites.ICON_RESOURCE, r.getResourceName(iconResId));
Adam Cohen228da5a2011-07-27 22:23:47 -07001055 values.put(Favorites._ID, id);
Mike Cleronb87bd162009-10-30 16:36:56 -07001056
Adam Cohen228da5a2011-07-27 22:23:47 -07001057 if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values) < 0) {
1058 return -1;
1059 }
1060 return id;
Mike Cleronb87bd162009-10-30 16:36:56 -07001061 }
1062 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -07001063
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001064 /**
1065 * Build a query string that will match any row where the column matches
1066 * anything in the values list.
1067 */
1068 static String buildOrWhereString(String column, int[] values) {
1069 StringBuilder selectWhere = new StringBuilder();
1070 for (int i = values.length - 1; i >= 0; i--) {
1071 selectWhere.append(column).append("=").append(values[i]);
1072 if (i > 0) {
1073 selectWhere.append(" OR ");
1074 }
1075 }
1076 return selectWhere.toString();
1077 }
1078
1079 static class SqlArguments {
1080 public final String table;
1081 public final String where;
1082 public final String[] args;
1083
1084 SqlArguments(Uri url, String where, String[] args) {
1085 if (url.getPathSegments().size() == 1) {
1086 this.table = url.getPathSegments().get(0);
1087 this.where = where;
1088 this.args = args;
1089 } else if (url.getPathSegments().size() != 2) {
1090 throw new IllegalArgumentException("Invalid URI: " + url);
1091 } else if (!TextUtils.isEmpty(where)) {
1092 throw new UnsupportedOperationException("WHERE clause not supported: " + url);
1093 } else {
1094 this.table = url.getPathSegments().get(0);
Daniel Lehmannc3a80402012-04-23 21:35:11 -07001095 this.where = "_id=" + ContentUris.parseId(url);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001096 this.args = null;
1097 }
1098 }
1099
1100 SqlArguments(Uri url) {
1101 if (url.getPathSegments().size() == 1) {
1102 table = url.getPathSegments().get(0);
1103 where = null;
1104 args = null;
1105 } else {
1106 throw new IllegalArgumentException("Invalid URI: " + url);
1107 }
1108 }
1109 }
1110}