blob: 8097ac99efa48cc597142e4ba2651765a3144a53 [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 Lehmannd02402c2012-05-14 18:30:53 -070069 private static final int DATABASE_VERSION = 12;
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";
Michael Jurka45355c42012-10-08 13:21:35 +020077 static final String DEFAULT_WORKSPACE_RESOURCE_ID =
78 "DEFAULT_WORKSPACE_RESOURCE_ID";
The Android Open Source Project31dd5032009-03-03 19:32:27 -080079
Winson Chungb3302ae2012-05-01 10:19:14 -070080 private static final String ACTION_APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE =
81 "com.android.launcher.action.APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE";
82
Jeffrey Sharkey2bbcae12009-03-31 14:37:57 -070083 /**
Romain Guy73b979d2009-06-09 12:57:21 -070084 * {@link Uri} triggered at any registered {@link android.database.ContentObserver} when
Jeffrey Sharkey2bbcae12009-03-31 14:37:57 -070085 * {@link AppWidgetHost#deleteHost()} is called during database creation.
86 * Use this to recall {@link AppWidgetHost#startListening()} if needed.
87 */
88 static final Uri CONTENT_APPWIDGET_RESET_URI =
89 Uri.parse("content://" + AUTHORITY + "/appWidgetReset");
Daniel Lehmannc3a80402012-04-23 21:35:11 -070090
Michael Jurkaa8c760d2011-04-28 14:59:33 -070091 private DatabaseHelper mOpenHelper;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080092
93 @Override
94 public boolean onCreate() {
95 mOpenHelper = new DatabaseHelper(getContext());
Michael Jurkaa8c760d2011-04-28 14:59:33 -070096 ((LauncherApplication) getContext()).setLauncherProvider(this);
The Android Open Source Project31dd5032009-03-03 19:32:27 -080097 return true;
98 }
99
100 @Override
101 public String getType(Uri uri) {
102 SqlArguments args = new SqlArguments(uri, null, null);
103 if (TextUtils.isEmpty(args.where)) {
104 return "vnd.android.cursor.dir/" + args.table;
105 } else {
106 return "vnd.android.cursor.item/" + args.table;
107 }
108 }
109
110 @Override
111 public Cursor query(Uri uri, String[] projection, String selection,
112 String[] selectionArgs, String sortOrder) {
113
114 SqlArguments args = new SqlArguments(uri, selection, selectionArgs);
115 SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
116 qb.setTables(args.table);
117
Romain Guy73b979d2009-06-09 12:57:21 -0700118 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800119 Cursor result = qb.query(db, projection, args.where, args.args, null, null, sortOrder);
120 result.setNotificationUri(getContext().getContentResolver(), uri);
121
122 return result;
123 }
124
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700125 private static long dbInsertAndCheck(DatabaseHelper helper,
126 SQLiteDatabase db, String table, String nullColumnHack, ContentValues values) {
127 if (!values.containsKey(LauncherSettings.Favorites._ID)) {
128 throw new RuntimeException("Error: attempting to add item without specifying an id");
129 }
130 return db.insert(table, nullColumnHack, values);
131 }
132
Adam Cohen228da5a2011-07-27 22:23:47 -0700133 private static void deleteId(SQLiteDatabase db, long id) {
134 Uri uri = LauncherSettings.Favorites.getContentUri(id, false);
135 SqlArguments args = new SqlArguments(uri, null, null);
136 db.delete(args.table, args.where, args.args);
137 }
138
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800139 @Override
140 public Uri insert(Uri uri, ContentValues initialValues) {
141 SqlArguments args = new SqlArguments(uri);
142
143 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700144 final long rowId = dbInsertAndCheck(mOpenHelper, db, args.table, null, initialValues);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800145 if (rowId <= 0) return null;
146
147 uri = ContentUris.withAppendedId(uri, rowId);
148 sendNotify(uri);
149
150 return uri;
151 }
152
153 @Override
154 public int bulkInsert(Uri uri, ContentValues[] values) {
155 SqlArguments args = new SqlArguments(uri);
156
157 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
158 db.beginTransaction();
159 try {
160 int numValues = values.length;
161 for (int i = 0; i < numValues; i++) {
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700162 if (dbInsertAndCheck(mOpenHelper, db, args.table, null, values[i]) < 0) {
163 return 0;
164 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800165 }
166 db.setTransactionSuccessful();
167 } finally {
168 db.endTransaction();
169 }
170
171 sendNotify(uri);
172 return values.length;
173 }
174
175 @Override
176 public int delete(Uri uri, String selection, String[] selectionArgs) {
177 SqlArguments args = new SqlArguments(uri, selection, selectionArgs);
178
179 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
180 int count = db.delete(args.table, args.where, args.args);
181 if (count > 0) sendNotify(uri);
182
183 return count;
184 }
185
186 @Override
187 public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
188 SqlArguments args = new SqlArguments(uri, selection, selectionArgs);
189
190 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
191 int count = db.update(args.table, values, args.where, args.args);
192 if (count > 0) sendNotify(uri);
193
194 return count;
195 }
196
197 private void sendNotify(Uri uri) {
198 String notify = uri.getQueryParameter(PARAMETER_NOTIFY);
199 if (notify == null || "true".equals(notify)) {
200 getContext().getContentResolver().notifyChange(uri, null);
201 }
202 }
203
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700204 public long generateNewId() {
205 return mOpenHelper.generateNewId();
206 }
207
Brian Muramatsu5524b492012-10-02 16:55:54 -0700208 /**
209 * @param workspaceResId that can be 0 to use default or non-zero for specific resource
210 */
Michael Jurka45355c42012-10-08 13:21:35 +0200211 synchronized public void loadDefaultFavoritesIfNecessary(int origWorkspaceResId) {
Michael Jurkab85f8a42012-04-25 15:48:32 -0700212 String spKey = LauncherApplication.getSharedPreferencesKey();
213 SharedPreferences sp = getContext().getSharedPreferences(spKey, Context.MODE_PRIVATE);
214 if (sp.getBoolean(DB_CREATED_BUT_DEFAULT_WORKSPACE_NOT_LOADED, false)) {
Michael Jurka45355c42012-10-08 13:21:35 +0200215 int workspaceResId = origWorkspaceResId;
216
Brian Muramatsu5524b492012-10-02 16:55:54 -0700217 // Use default workspace resource if none provided
218 if (workspaceResId == 0) {
Michael Jurka45355c42012-10-08 13:21:35 +0200219 workspaceResId = sp.getInt(DEFAULT_WORKSPACE_RESOURCE_ID, R.xml.default_workspace);
Brian Muramatsu5524b492012-10-02 16:55:54 -0700220 }
221
Michael Jurkab85f8a42012-04-25 15:48:32 -0700222 // Populate favorites table with initial favorites
223 SharedPreferences.Editor editor = sp.edit();
224 editor.remove(DB_CREATED_BUT_DEFAULT_WORKSPACE_NOT_LOADED);
Michael Jurka45355c42012-10-08 13:21:35 +0200225 if (origWorkspaceResId != 0) {
226 editor.putInt(DEFAULT_WORKSPACE_RESOURCE_ID, origWorkspaceResId);
227 }
Brian Muramatsu5524b492012-10-02 16:55:54 -0700228 mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(), workspaceResId);
Michael Jurkab85f8a42012-04-25 15:48:32 -0700229 editor.commit();
230 }
231 }
232
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800233 private static class DatabaseHelper extends SQLiteOpenHelper {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800234 private static final String TAG_FAVORITES = "favorites";
235 private static final String TAG_FAVORITE = "favorite";
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700236 private static final String TAG_CLOCK = "clock";
237 private static final String TAG_SEARCH = "search";
Mike Cleronb87bd162009-10-30 16:36:56 -0700238 private static final String TAG_APPWIDGET = "appwidget";
239 private static final String TAG_SHORTCUT = "shortcut";
Adam Cohen228da5a2011-07-27 22:23:47 -0700240 private static final String TAG_FOLDER = "folder";
Winson Chungb3302ae2012-05-01 10:19:14 -0700241 private static final String TAG_EXTRA = "extra";
Winson Chung3d503fb2011-07-13 17:25:49 -0700242
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800243 private final Context mContext;
The Android Open Source Project7376fae2009-03-11 12:11:58 -0700244 private final AppWidgetHost mAppWidgetHost;
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700245 private long mMaxId = -1;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800246
247 DatabaseHelper(Context context) {
248 super(context, DATABASE_NAME, null, DATABASE_VERSION);
249 mContext = context;
The Android Open Source Project7376fae2009-03-11 12:11:58 -0700250 mAppWidgetHost = new AppWidgetHost(context, Launcher.APPWIDGET_HOST_ID);
Winson Chung3d503fb2011-07-13 17:25:49 -0700251
252 // In the case where neither onCreate nor onUpgrade gets called, we read the maxId from
253 // the DB here
254 if (mMaxId == -1) {
255 mMaxId = initializeMaxId(getWritableDatabase());
256 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800257 }
258
Jeffrey Sharkey2bbcae12009-03-31 14:37:57 -0700259 /**
260 * Send notification that we've deleted the {@link AppWidgetHost},
261 * probably as part of the initial database creation. The receiver may
262 * want to re-call {@link AppWidgetHost#startListening()} to ensure
263 * callbacks are correctly set.
264 */
265 private void sendAppWidgetResetNotify() {
266 final ContentResolver resolver = mContext.getContentResolver();
267 resolver.notifyChange(CONTENT_APPWIDGET_RESET_URI, null);
268 }
269
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800270 @Override
271 public void onCreate(SQLiteDatabase db) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800272 if (LOGD) Log.d(TAG, "creating new launcher database");
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700273
274 mMaxId = 1;
275
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800276 db.execSQL("CREATE TABLE favorites (" +
277 "_id INTEGER PRIMARY KEY," +
278 "title TEXT," +
279 "intent TEXT," +
280 "container INTEGER," +
281 "screen INTEGER," +
282 "cellX INTEGER," +
283 "cellY INTEGER," +
284 "spanX INTEGER," +
285 "spanY INTEGER," +
286 "itemType INTEGER," +
The Android Open Source Projectca9475f2009-03-13 13:04:24 -0700287 "appWidgetId INTEGER NOT NULL DEFAULT -1," +
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800288 "isShortcut INTEGER," +
289 "iconType INTEGER," +
290 "iconPackage TEXT," +
291 "iconResource TEXT," +
292 "icon BLOB," +
293 "uri TEXT," +
294 "displayMode INTEGER" +
295 ");");
296
The Android Open Source Project7376fae2009-03-11 12:11:58 -0700297 // Database was just created, so wipe any previous widgets
298 if (mAppWidgetHost != null) {
299 mAppWidgetHost.deleteHost();
Jeffrey Sharkey2bbcae12009-03-31 14:37:57 -0700300 sendAppWidgetResetNotify();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800301 }
Winson Chung3d503fb2011-07-13 17:25:49 -0700302
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800303 if (!convertDatabase(db)) {
Michael Jurkab85f8a42012-04-25 15:48:32 -0700304 // Set a shared pref so that we know we need to load the default workspace later
305 setFlagToLoadDefaultWorkspaceLater();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800306 }
307 }
308
Michael Jurkab85f8a42012-04-25 15:48:32 -0700309 private void setFlagToLoadDefaultWorkspaceLater() {
310 String spKey = LauncherApplication.getSharedPreferencesKey();
311 SharedPreferences sp = mContext.getSharedPreferences(spKey, Context.MODE_PRIVATE);
312 SharedPreferences.Editor editor = sp.edit();
313 editor.putBoolean(DB_CREATED_BUT_DEFAULT_WORKSPACE_NOT_LOADED, true);
314 editor.commit();
315 }
316
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800317 private boolean convertDatabase(SQLiteDatabase db) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800318 if (LOGD) Log.d(TAG, "converting database from an older format, but not onUpgrade");
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800319 boolean converted = false;
320
321 final Uri uri = Uri.parse("content://" + Settings.AUTHORITY +
322 "/old_favorites?notify=true");
323 final ContentResolver resolver = mContext.getContentResolver();
324 Cursor cursor = null;
325
326 try {
327 cursor = resolver.query(uri, null, null, null, null);
328 } catch (Exception e) {
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700329 // Ignore
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800330 }
331
332 // We already have a favorites database in the old provider
333 if (cursor != null && cursor.getCount() > 0) {
334 try {
335 converted = copyFromCursor(db, cursor) > 0;
336 } finally {
337 cursor.close();
338 }
339
340 if (converted) {
341 resolver.delete(uri, null, null);
342 }
343 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700344
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800345 if (converted) {
The Android Open Source Project7376fae2009-03-11 12:11:58 -0700346 // Convert widgets from this import into widgets
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800347 if (LOGD) Log.d(TAG, "converted and now triggering widget upgrade");
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800348 convertWidgets(db);
349 }
350
351 return converted;
352 }
353
354 private int copyFromCursor(SQLiteDatabase db, Cursor c) {
Romain Guy73b979d2009-06-09 12:57:21 -0700355 final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800356 final int intentIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.INTENT);
357 final int titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE);
358 final int iconTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_TYPE);
359 final int iconIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON);
360 final int iconPackageIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_PACKAGE);
361 final int iconResourceIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_RESOURCE);
362 final int containerIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER);
363 final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE);
364 final int screenIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN);
365 final int cellXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX);
366 final int cellYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY);
367 final int uriIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.URI);
368 final int displayModeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.DISPLAY_MODE);
369
370 ContentValues[] rows = new ContentValues[c.getCount()];
371 int i = 0;
372 while (c.moveToNext()) {
373 ContentValues values = new ContentValues(c.getColumnCount());
Romain Guy73b979d2009-06-09 12:57:21 -0700374 values.put(LauncherSettings.Favorites._ID, c.getLong(idIndex));
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800375 values.put(LauncherSettings.Favorites.INTENT, c.getString(intentIndex));
376 values.put(LauncherSettings.Favorites.TITLE, c.getString(titleIndex));
377 values.put(LauncherSettings.Favorites.ICON_TYPE, c.getInt(iconTypeIndex));
378 values.put(LauncherSettings.Favorites.ICON, c.getBlob(iconIndex));
379 values.put(LauncherSettings.Favorites.ICON_PACKAGE, c.getString(iconPackageIndex));
380 values.put(LauncherSettings.Favorites.ICON_RESOURCE, c.getString(iconResourceIndex));
381 values.put(LauncherSettings.Favorites.CONTAINER, c.getInt(containerIndex));
382 values.put(LauncherSettings.Favorites.ITEM_TYPE, c.getInt(itemTypeIndex));
The Android Open Source Project7376fae2009-03-11 12:11:58 -0700383 values.put(LauncherSettings.Favorites.APPWIDGET_ID, -1);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800384 values.put(LauncherSettings.Favorites.SCREEN, c.getInt(screenIndex));
385 values.put(LauncherSettings.Favorites.CELLX, c.getInt(cellXIndex));
386 values.put(LauncherSettings.Favorites.CELLY, c.getInt(cellYIndex));
387 values.put(LauncherSettings.Favorites.URI, c.getString(uriIndex));
388 values.put(LauncherSettings.Favorites.DISPLAY_MODE, c.getInt(displayModeIndex));
389 rows[i++] = values;
390 }
391
392 db.beginTransaction();
393 int total = 0;
394 try {
395 int numValues = rows.length;
396 for (i = 0; i < numValues; i++) {
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700397 if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, rows[i]) < 0) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800398 return 0;
399 } else {
400 total++;
401 }
402 }
403 db.setTransactionSuccessful();
404 } finally {
405 db.endTransaction();
406 }
407
408 return total;
409 }
410
411 @Override
412 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800413 if (LOGD) Log.d(TAG, "onUpgrade triggered");
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700414
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800415 int version = oldVersion;
The Android Open Source Projectca9475f2009-03-13 13:04:24 -0700416 if (version < 3) {
417 // upgrade 1,2 -> 3 added appWidgetId column
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800418 db.beginTransaction();
419 try {
The Android Open Source Project7376fae2009-03-11 12:11:58 -0700420 // Insert new column for holding appWidgetIds
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800421 db.execSQL("ALTER TABLE favorites " +
The Android Open Source Projectca9475f2009-03-13 13:04:24 -0700422 "ADD COLUMN appWidgetId INTEGER NOT NULL DEFAULT -1;");
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800423 db.setTransactionSuccessful();
The Android Open Source Projectca9475f2009-03-13 13:04:24 -0700424 version = 3;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800425 } catch (SQLException ex) {
426 // Old version remains, which means we wipe old data
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800427 Log.e(TAG, ex.getMessage(), ex);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800428 } finally {
429 db.endTransaction();
430 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700431
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800432 // Convert existing widgets only if table upgrade was successful
The Android Open Source Projectca9475f2009-03-13 13:04:24 -0700433 if (version == 3) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800434 convertWidgets(db);
435 }
436 }
Romain Guy73b979d2009-06-09 12:57:21 -0700437
438 if (version < 4) {
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800439 version = 4;
Romain Guy73b979d2009-06-09 12:57:21 -0700440 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700441
Romain Guy509cd6a2010-03-23 15:10:56 -0700442 // Where's version 5?
443 // - Donut and sholes on 2.0 shipped with version 4 of launcher1.
444 // - Passion shipped on 2.1 with version 6 of launcher2
445 // - Sholes shipped on 2.1r1 (aka Mr. 3) with version 5 of launcher 1
446 // but version 5 on there was the updateContactsShortcuts change
447 // which was version 6 in launcher 2 (first shipped on passion 2.1r1).
448 // The updateContactsShortcuts change is idempotent, so running it twice
449 // is okay so we'll do that when upgrading the devices that shipped with it.
450 if (version < 6) {
Mike Cleron3a2b3f22009-11-05 17:17:42 -0800451 // We went from 3 to 5 screens. Move everything 1 to the right
452 db.beginTransaction();
453 try {
454 db.execSQL("UPDATE favorites SET screen=(screen + 1);");
455 db.setTransactionSuccessful();
Mike Cleron3a2b3f22009-11-05 17:17:42 -0800456 } catch (SQLException ex) {
457 // Old version remains, which means we wipe old data
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800458 Log.e(TAG, ex.getMessage(), ex);
Mike Cleron3a2b3f22009-11-05 17:17:42 -0800459 } finally {
460 db.endTransaction();
461 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700462
Romain Guy509cd6a2010-03-23 15:10:56 -0700463 // We added the fast track.
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800464 if (updateContactsShortcuts(db)) {
465 version = 6;
466 }
467 }
Bjorn Bringert7984c942009-12-09 15:38:25 +0000468
469 if (version < 7) {
470 // Version 7 gets rid of the special search widget.
471 convertWidgets(db);
472 version = 7;
473 }
474
Joe Onorato0589f0f2010-02-08 13:44:00 -0800475 if (version < 8) {
476 // Version 8 (froyo) has the icons all normalized. This should
477 // already be the case in practice, but we now rely on it and don't
478 // resample the images each time.
479 normalizeIcons(db);
480 version = 8;
481 }
482
Winson Chung3d503fb2011-07-13 17:25:49 -0700483 if (version < 9) {
484 // The max id is not yet set at this point (onUpgrade is triggered in the ctor
485 // before it gets a change to get set, so we need to read it here when we use it)
486 if (mMaxId == -1) {
487 mMaxId = initializeMaxId(db);
488 }
489
490 // Add default hotseat icons
Winson Chung6d092682011-11-16 18:43:26 -0800491 loadFavorites(db, R.xml.update_workspace);
Winson Chung3d503fb2011-07-13 17:25:49 -0700492 version = 9;
493 }
494
Daniel Lehmannd02402c2012-05-14 18:30:53 -0700495 // We bumped the version three time during JB, once to update the launch flags, once to
496 // update the override for the default launch animation and once to set the mimetype
497 // to improve startup performance
498 if (version < 12) {
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700499 // Contact shortcuts need a different set of flags to be launched now
500 // The updateContactsShortcuts change is idempotent, so we can keep using it like
501 // back in the Donut days
502 updateContactsShortcuts(db);
Daniel Lehmannd02402c2012-05-14 18:30:53 -0700503 version = 12;
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700504 }
505
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800506 if (version != DATABASE_VERSION) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800507 Log.w(TAG, "Destroying all old data.");
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800508 db.execSQL("DROP TABLE IF EXISTS " + TABLE_FAVORITES);
509 onCreate(db);
510 }
511 }
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800512
513 private boolean updateContactsShortcuts(SQLiteDatabase db) {
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800514 final String selectWhere = buildOrWhereString(Favorites.ITEM_TYPE,
515 new int[] { Favorites.ITEM_TYPE_SHORTCUT });
516
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700517 Cursor c = null;
518 final String actionQuickContact = "com.android.contacts.action.QUICK_CONTACT";
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800519 db.beginTransaction();
520 try {
521 // Select and iterate through each matching widget
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700522 c = db.query(TABLE_FAVORITES,
523 new String[] { Favorites._ID, Favorites.INTENT },
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800524 selectWhere, null, null, null, null);
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700525 if (c == null) return false;
526
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800527 if (LOGD) Log.d(TAG, "found upgrade cursor count=" + c.getCount());
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700528
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800529 final int idIndex = c.getColumnIndex(Favorites._ID);
530 final int intentIndex = c.getColumnIndex(Favorites.INTENT);
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700531
532 while (c.moveToNext()) {
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800533 long favoriteId = c.getLong(idIndex);
534 final String intentUri = c.getString(intentIndex);
535 if (intentUri != null) {
536 try {
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700537 final Intent intent = Intent.parseUri(intentUri, 0);
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800538 android.util.Log.d("Home", intent.toString());
539 final Uri uri = intent.getData();
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700540 if (uri != null) {
541 final String data = uri.toString();
542 if ((Intent.ACTION_VIEW.equals(intent.getAction()) ||
543 actionQuickContact.equals(intent.getAction())) &&
544 (data.startsWith("content://contacts/people/") ||
545 data.startsWith("content://com.android.contacts/" +
546 "contacts/lookup/"))) {
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800547
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700548 final Intent newIntent = new Intent(actionQuickContact);
549 // When starting from the launcher, start in a new, cleared task
550 // CLEAR_WHEN_TASK_RESET cannot reset the root of a task, so we
551 // clear the whole thing preemptively here since
552 // QuickContactActivity will finish itself when launching other
553 // detail activities.
554 newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
555 Intent.FLAG_ACTIVITY_CLEAR_TASK);
Winson Chung2672ff92012-05-04 16:22:30 -0700556 newIntent.putExtra(
557 Launcher.INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION, true);
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700558 newIntent.setData(uri);
Daniel Lehmannd02402c2012-05-14 18:30:53 -0700559 // Determine the type and also put that in the shortcut
560 // (that can speed up launch a bit)
561 newIntent.setDataAndType(uri, newIntent.resolveType(mContext));
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800562
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700563 final ContentValues values = new ContentValues();
564 values.put(LauncherSettings.Favorites.INTENT,
565 newIntent.toUri(0));
566
567 String updateWhere = Favorites._ID + "=" + favoriteId;
568 db.update(TABLE_FAVORITES, values, updateWhere, null);
569 }
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800570 }
571 } catch (RuntimeException ex) {
572 Log.e(TAG, "Problem upgrading shortcut", ex);
573 } catch (URISyntaxException e) {
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700574 Log.e(TAG, "Problem upgrading shortcut", e);
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800575 }
576 }
577 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700578
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800579 db.setTransactionSuccessful();
580 } catch (SQLException ex) {
581 Log.w(TAG, "Problem while upgrading contacts", ex);
582 return false;
583 } finally {
584 db.endTransaction();
585 if (c != null) {
586 c.close();
587 }
588 }
589
590 return true;
591 }
592
Joe Onorato0589f0f2010-02-08 13:44:00 -0800593 private void normalizeIcons(SQLiteDatabase db) {
594 Log.d(TAG, "normalizing icons");
595
Joe Onorato346e1292010-02-18 10:34:24 -0500596 db.beginTransaction();
Joe Onorato0589f0f2010-02-08 13:44:00 -0800597 Cursor c = null;
Joe Onorato9690b392010-03-23 17:34:37 -0400598 SQLiteStatement update = null;
Joe Onorato0589f0f2010-02-08 13:44:00 -0800599 try {
600 boolean logged = false;
Joe Onorato9690b392010-03-23 17:34:37 -0400601 update = db.compileStatement("UPDATE favorites "
Jeff Hamiltoneaf77d62010-02-13 00:08:17 -0600602 + "SET icon=? WHERE _id=?");
Joe Onorato0589f0f2010-02-08 13:44:00 -0800603
604 c = db.rawQuery("SELECT _id, icon FROM favorites WHERE iconType=" +
605 Favorites.ICON_TYPE_BITMAP, null);
606
607 final int idIndex = c.getColumnIndexOrThrow(Favorites._ID);
608 final int iconIndex = c.getColumnIndexOrThrow(Favorites.ICON);
609
610 while (c.moveToNext()) {
611 long id = c.getLong(idIndex);
612 byte[] data = c.getBlob(iconIndex);
613 try {
614 Bitmap bitmap = Utilities.resampleIconBitmap(
615 BitmapFactory.decodeByteArray(data, 0, data.length),
616 mContext);
617 if (bitmap != null) {
618 update.bindLong(1, id);
619 data = ItemInfo.flattenBitmap(bitmap);
620 if (data != null) {
621 update.bindBlob(2, data);
622 update.execute();
623 }
624 bitmap.recycle();
Joe Onorato0589f0f2010-02-08 13:44:00 -0800625 }
626 } catch (Exception e) {
627 if (!logged) {
628 Log.e(TAG, "Failed normalizing icon " + id, e);
629 } else {
630 Log.e(TAG, "Also failed normalizing icon " + id);
631 }
632 logged = true;
633 }
634 }
Bjorn Bringert3a928e42010-02-19 11:15:40 +0000635 db.setTransactionSuccessful();
Joe Onorato0589f0f2010-02-08 13:44:00 -0800636 } catch (SQLException ex) {
637 Log.w(TAG, "Problem while allocating appWidgetIds for existing widgets", ex);
638 } finally {
639 db.endTransaction();
Joe Onorato9690b392010-03-23 17:34:37 -0400640 if (update != null) {
641 update.close();
642 }
Joe Onorato0589f0f2010-02-08 13:44:00 -0800643 if (c != null) {
644 c.close();
645 }
646 }
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700647 }
648
649 // Generates a new ID to use for an object in your database. This method should be only
650 // called from the main UI thread. As an exception, we do call it when we call the
651 // constructor from the worker thread; however, this doesn't extend until after the
652 // constructor is called, and we only pass a reference to LauncherProvider to LauncherApp
653 // after that point
654 public long generateNewId() {
655 if (mMaxId < 0) {
656 throw new RuntimeException("Error: max id was not initialized");
657 }
658 mMaxId += 1;
659 return mMaxId;
660 }
661
662 private long initializeMaxId(SQLiteDatabase db) {
663 Cursor c = db.rawQuery("SELECT MAX(_id) FROM favorites", null);
664
665 // get the result
666 final int maxIdIndex = 0;
667 long id = -1;
668 if (c != null && c.moveToNext()) {
669 id = c.getLong(maxIdIndex);
670 }
Michael Jurka5130e402011-10-13 04:55:35 -0700671 if (c != null) {
672 c.close();
673 }
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700674
675 if (id == -1) {
676 throw new RuntimeException("Error: could not query max id");
677 }
678
679 return id;
Joe Onorato0589f0f2010-02-08 13:44:00 -0800680 }
681
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800682 /**
The Android Open Source Project7376fae2009-03-11 12:11:58 -0700683 * Upgrade existing clock and photo frame widgets into their new widget
Bjorn Bringert93c45762009-12-16 13:19:47 +0000684 * equivalents.
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800685 */
686 private void convertWidgets(SQLiteDatabase db) {
Bjorn Bringert34251342009-12-15 13:33:11 +0000687 final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800688 final int[] bindSources = new int[] {
689 Favorites.ITEM_TYPE_WIDGET_CLOCK,
690 Favorites.ITEM_TYPE_WIDGET_PHOTO_FRAME,
Bjorn Bringert7984c942009-12-09 15:38:25 +0000691 Favorites.ITEM_TYPE_WIDGET_SEARCH,
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800692 };
Bjorn Bringert7984c942009-12-09 15:38:25 +0000693
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800694 final String selectWhere = buildOrWhereString(Favorites.ITEM_TYPE, bindSources);
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700695
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800696 Cursor c = null;
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700697
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800698 db.beginTransaction();
699 try {
700 // Select and iterate through each matching widget
Bjorn Bringert7984c942009-12-09 15:38:25 +0000701 c = db.query(TABLE_FAVORITES, new String[] { Favorites._ID, Favorites.ITEM_TYPE },
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800702 selectWhere, null, null, null, null);
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700703
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800704 if (LOGD) Log.d(TAG, "found upgrade cursor count=" + c.getCount());
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700705
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800706 final ContentValues values = new ContentValues();
707 while (c != null && c.moveToNext()) {
708 long favoriteId = c.getLong(0);
Bjorn Bringert7984c942009-12-09 15:38:25 +0000709 int favoriteType = c.getInt(1);
710
The Android Open Source Project7376fae2009-03-11 12:11:58 -0700711 // Allocate and update database with new appWidgetId
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800712 try {
The Android Open Source Project7376fae2009-03-11 12:11:58 -0700713 int appWidgetId = mAppWidgetHost.allocateAppWidgetId();
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700714
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800715 if (LOGD) {
716 Log.d(TAG, "allocated appWidgetId=" + appWidgetId
717 + " for favoriteId=" + favoriteId);
718 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800719 values.clear();
Bjorn Bringert7984c942009-12-09 15:38:25 +0000720 values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_APPWIDGET);
721 values.put(Favorites.APPWIDGET_ID, appWidgetId);
722
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800723 // Original widgets might not have valid spans when upgrading
Bjorn Bringert7984c942009-12-09 15:38:25 +0000724 if (favoriteType == Favorites.ITEM_TYPE_WIDGET_SEARCH) {
725 values.put(LauncherSettings.Favorites.SPANX, 4);
726 values.put(LauncherSettings.Favorites.SPANY, 1);
727 } else {
728 values.put(LauncherSettings.Favorites.SPANX, 2);
729 values.put(LauncherSettings.Favorites.SPANY, 2);
730 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800731
732 String updateWhere = Favorites._ID + "=" + favoriteId;
733 db.update(TABLE_FAVORITES, values, updateWhere, null);
Bjorn Bringert34251342009-12-15 13:33:11 +0000734
Bjorn Bringert34251342009-12-15 13:33:11 +0000735 if (favoriteType == Favorites.ITEM_TYPE_WIDGET_CLOCK) {
Michael Jurka8b805b12012-04-18 14:23:14 -0700736 // TODO: check return value
737 appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId,
Bjorn Bringert34251342009-12-15 13:33:11 +0000738 new ComponentName("com.android.alarmclock",
739 "com.android.alarmclock.AnalogAppWidgetProvider"));
740 } else if (favoriteType == Favorites.ITEM_TYPE_WIDGET_PHOTO_FRAME) {
Michael Jurka8b805b12012-04-18 14:23:14 -0700741 // TODO: check return value
742 appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId,
Bjorn Bringert34251342009-12-15 13:33:11 +0000743 new ComponentName("com.android.camera",
744 "com.android.camera.PhotoAppWidgetProvider"));
745 } else if (favoriteType == Favorites.ITEM_TYPE_WIDGET_SEARCH) {
Michael Jurka8b805b12012-04-18 14:23:14 -0700746 // TODO: check return value
747 appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId,
Bjorn Bringertcd8fec02010-01-14 13:26:43 +0000748 getSearchWidgetProvider());
Bjorn Bringert34251342009-12-15 13:33:11 +0000749 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800750 } catch (RuntimeException ex) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800751 Log.e(TAG, "Problem allocating appWidgetId", ex);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800752 }
753 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700754
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800755 db.setTransactionSuccessful();
756 } catch (SQLException ex) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800757 Log.w(TAG, "Problem while allocating appWidgetIds for existing widgets", ex);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800758 } finally {
759 db.endTransaction();
760 if (c != null) {
761 c.close();
762 }
763 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800764 }
765
Michael Jurka8b805b12012-04-18 14:23:14 -0700766 private static final void beginDocument(XmlPullParser parser, String firstElementName)
767 throws XmlPullParserException, IOException {
768 int type;
Michael Jurka9bc8eba2012-05-21 20:36:44 -0700769 while ((type = parser.next()) != XmlPullParser.START_TAG
770 && type != XmlPullParser.END_DOCUMENT) {
Michael Jurka8b805b12012-04-18 14:23:14 -0700771 ;
772 }
773
Michael Jurka9bc8eba2012-05-21 20:36:44 -0700774 if (type != XmlPullParser.START_TAG) {
Michael Jurka8b805b12012-04-18 14:23:14 -0700775 throw new XmlPullParserException("No start tag found");
776 }
777
778 if (!parser.getName().equals(firstElementName)) {
779 throw new XmlPullParserException("Unexpected start tag: found " + parser.getName() +
780 ", expected " + firstElementName);
781 }
782 }
783
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800784 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800785 * Loads the default set of favorite packages from an xml file.
786 *
787 * @param db The database to write the values into
Winson Chung3d503fb2011-07-13 17:25:49 -0700788 * @param filterContainerId The specific container id of items to load
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800789 */
Winson Chung6d092682011-11-16 18:43:26 -0800790 private int loadFavorites(SQLiteDatabase db, int workspaceResourceId) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800791 Intent intent = new Intent(Intent.ACTION_MAIN, null);
792 intent.addCategory(Intent.CATEGORY_LAUNCHER);
793 ContentValues values = new ContentValues();
794
795 PackageManager packageManager = mContext.getPackageManager();
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800796 int allAppsButtonRank =
797 mContext.getResources().getInteger(R.integer.hotseat_all_apps_index);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800798 int i = 0;
799 try {
Winson Chung6d092682011-11-16 18:43:26 -0800800 XmlResourceParser parser = mContext.getResources().getXml(workspaceResourceId);
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700801 AttributeSet attrs = Xml.asAttributeSet(parser);
Michael Jurka8b805b12012-04-18 14:23:14 -0700802 beginDocument(parser, TAG_FAVORITES);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800803
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700804 final int depth = parser.getDepth();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800805
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700806 int type;
807 while (((type = parser.next()) != XmlPullParser.END_TAG ||
808 parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
809
810 if (type != XmlPullParser.START_TAG) {
811 continue;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800812 }
813
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700814 boolean added = false;
815 final String name = parser.getName();
816
817 TypedArray a = mContext.obtainStyledAttributes(attrs, R.styleable.Favorite);
818
Winson Chung3d503fb2011-07-13 17:25:49 -0700819 long container = LauncherSettings.Favorites.CONTAINER_DESKTOP;
820 if (a.hasValue(R.styleable.Favorite_container)) {
821 container = Long.valueOf(a.getString(R.styleable.Favorite_container));
822 }
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700823
Winson Chung6d092682011-11-16 18:43:26 -0800824 String screen = a.getString(R.styleable.Favorite_screen);
825 String x = a.getString(R.styleable.Favorite_x);
826 String y = a.getString(R.styleable.Favorite_y);
827
828 // If we are adding to the hotseat, the screen is used as the position in the
829 // hotseat. This screen can't be at position 0 because AllApps is in the
830 // zeroth position.
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800831 if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT
832 && Integer.valueOf(screen) == allAppsButtonRank) {
Winson Chung6d092682011-11-16 18:43:26 -0800833 throw new RuntimeException("Invalid screen position for hotseat item");
834 }
835
836 values.clear();
837 values.put(LauncherSettings.Favorites.CONTAINER, container);
838 values.put(LauncherSettings.Favorites.SCREEN, screen);
839 values.put(LauncherSettings.Favorites.CELLX, x);
840 values.put(LauncherSettings.Favorites.CELLY, y);
841
842 if (TAG_FAVORITE.equals(name)) {
843 long id = addAppShortcut(db, values, a, packageManager, intent);
844 added = id >= 0;
845 } else if (TAG_SEARCH.equals(name)) {
846 added = addSearchWidget(db, values);
847 } else if (TAG_CLOCK.equals(name)) {
848 added = addClockWidget(db, values);
849 } else if (TAG_APPWIDGET.equals(name)) {
Winson Chungb3302ae2012-05-01 10:19:14 -0700850 added = addAppWidget(parser, attrs, type, db, values, a, packageManager);
Winson Chung6d092682011-11-16 18:43:26 -0800851 } else if (TAG_SHORTCUT.equals(name)) {
852 long id = addUriShortcut(db, values, a);
853 added = id >= 0;
854 } else if (TAG_FOLDER.equals(name)) {
855 String title;
856 int titleResId = a.getResourceId(R.styleable.Favorite_title, -1);
857 if (titleResId != -1) {
858 title = mContext.getResources().getString(titleResId);
859 } else {
860 title = mContext.getResources().getString(R.string.folder_name);
Winson Chung3d503fb2011-07-13 17:25:49 -0700861 }
Winson Chung6d092682011-11-16 18:43:26 -0800862 values.put(LauncherSettings.Favorites.TITLE, title);
863 long folderId = addFolder(db, values);
864 added = folderId >= 0;
Winson Chung3d503fb2011-07-13 17:25:49 -0700865
Winson Chung6d092682011-11-16 18:43:26 -0800866 ArrayList<Long> folderItems = new ArrayList<Long>();
Winson Chung3d503fb2011-07-13 17:25:49 -0700867
Winson Chung6d092682011-11-16 18:43:26 -0800868 int folderDepth = parser.getDepth();
869 while ((type = parser.next()) != XmlPullParser.END_TAG ||
870 parser.getDepth() > folderDepth) {
871 if (type != XmlPullParser.START_TAG) {
872 continue;
873 }
874 final String folder_item_name = parser.getName();
875
876 TypedArray ar = mContext.obtainStyledAttributes(attrs,
877 R.styleable.Favorite);
878 values.clear();
879 values.put(LauncherSettings.Favorites.CONTAINER, folderId);
880
881 if (TAG_FAVORITE.equals(folder_item_name) && folderId >= 0) {
882 long id =
883 addAppShortcut(db, values, ar, packageManager, intent);
884 if (id >= 0) {
885 folderItems.add(id);
886 }
887 } else if (TAG_SHORTCUT.equals(folder_item_name) && folderId >= 0) {
888 long id = addUriShortcut(db, values, ar);
889 if (id >= 0) {
890 folderItems.add(id);
891 }
Adam Cohen228da5a2011-07-27 22:23:47 -0700892 } else {
Winson Chung6d092682011-11-16 18:43:26 -0800893 throw new RuntimeException("Folders can " +
894 "contain only shortcuts");
Adam Cohen228da5a2011-07-27 22:23:47 -0700895 }
Winson Chung6d092682011-11-16 18:43:26 -0800896 ar.recycle();
897 }
898 // We can only have folders with >= 2 items, so we need to remove the
899 // folder and clean up if less than 2 items were included, or some
900 // failed to add, and less than 2 were actually added
901 if (folderItems.size() < 2 && folderId >= 0) {
902 // We just delete the folder and any items that made it
903 deleteId(db, folderId);
904 if (folderItems.size() > 0) {
905 deleteId(db, folderItems.get(0));
Adam Cohen228da5a2011-07-27 22:23:47 -0700906 }
Winson Chung6d092682011-11-16 18:43:26 -0800907 added = false;
Winson Chung3d503fb2011-07-13 17:25:49 -0700908 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800909 }
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700910 if (added) i++;
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700911 a.recycle();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800912 }
913 } catch (XmlPullParserException e) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800914 Log.w(TAG, "Got exception parsing favorites.", e);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800915 } catch (IOException e) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800916 Log.w(TAG, "Got exception parsing favorites.", e);
Winson Chung3d503fb2011-07-13 17:25:49 -0700917 } catch (RuntimeException e) {
918 Log.w(TAG, "Got exception parsing favorites.", e);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800919 }
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700920
921 return i;
922 }
923
Adam Cohen228da5a2011-07-27 22:23:47 -0700924 private long addAppShortcut(SQLiteDatabase db, ContentValues values, TypedArray a,
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700925 PackageManager packageManager, Intent intent) {
Adam Cohen228da5a2011-07-27 22:23:47 -0700926 long id = -1;
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700927 ActivityInfo info;
928 String packageName = a.getString(R.styleable.Favorite_packageName);
929 String className = a.getString(R.styleable.Favorite_className);
930 try {
Romain Guy693599f2010-03-23 10:58:18 -0700931 ComponentName cn;
932 try {
933 cn = new ComponentName(packageName, className);
934 info = packageManager.getActivityInfo(cn, 0);
935 } catch (PackageManager.NameNotFoundException nnfe) {
936 String[] packages = packageManager.currentToCanonicalPackageNames(
937 new String[] { packageName });
938 cn = new ComponentName(packages[0], className);
939 info = packageManager.getActivityInfo(cn, 0);
940 }
Adam Cohen228da5a2011-07-27 22:23:47 -0700941 id = generateNewId();
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700942 intent.setComponent(cn);
Romain Guy693599f2010-03-23 10:58:18 -0700943 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
944 Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
Romain Guy1ce1a242009-06-23 17:34:54 -0700945 values.put(Favorites.INTENT, intent.toUri(0));
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700946 values.put(Favorites.TITLE, info.loadLabel(packageManager).toString());
947 values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_APPLICATION);
948 values.put(Favorites.SPANX, 1);
949 values.put(Favorites.SPANY, 1);
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700950 values.put(Favorites._ID, generateNewId());
Adam Cohen228da5a2011-07-27 22:23:47 -0700951 if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values) < 0) {
952 return -1;
953 }
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700954 } catch (PackageManager.NameNotFoundException e) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800955 Log.w(TAG, "Unable to add favorite: " + packageName +
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700956 "/" + className, e);
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700957 }
Adam Cohen228da5a2011-07-27 22:23:47 -0700958 return id;
959 }
960
961 private long addFolder(SQLiteDatabase db, ContentValues values) {
962 values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_FOLDER);
963 values.put(Favorites.SPANX, 1);
964 values.put(Favorites.SPANY, 1);
965 long id = generateNewId();
966 values.put(Favorites._ID, id);
967 if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values) <= 0) {
968 return -1;
969 } else {
970 return id;
971 }
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700972 }
973
Bjorn Bringertcd8fec02010-01-14 13:26:43 +0000974 private ComponentName getSearchWidgetProvider() {
975 SearchManager searchManager =
976 (SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE);
977 ComponentName searchComponent = searchManager.getGlobalSearchActivity();
978 if (searchComponent == null) return null;
979 return getProviderInPackage(searchComponent.getPackageName());
980 }
981
982 /**
983 * Gets an appwidget provider from the given package. If the package contains more than
984 * one appwidget provider, an arbitrary one is returned.
985 */
986 private ComponentName getProviderInPackage(String packageName) {
987 AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext);
988 List<AppWidgetProviderInfo> providers = appWidgetManager.getInstalledProviders();
989 if (providers == null) return null;
990 final int providerCount = providers.size();
991 for (int i = 0; i < providerCount; i++) {
992 ComponentName provider = providers.get(i).provider;
993 if (provider != null && provider.getPackageName().equals(packageName)) {
994 return provider;
995 }
996 }
997 return null;
998 }
999
The Android Open Source Projectf96811c2009-03-18 17:39:48 -07001000 private boolean addSearchWidget(SQLiteDatabase db, ContentValues values) {
Bjorn Bringertcd8fec02010-01-14 13:26:43 +00001001 ComponentName cn = getSearchWidgetProvider();
Winson Chungb3302ae2012-05-01 10:19:14 -07001002 return addAppWidget(db, values, cn, 4, 1, null);
The Android Open Source Projectf96811c2009-03-18 17:39:48 -07001003 }
1004
1005 private boolean addClockWidget(SQLiteDatabase db, ContentValues values) {
Bjorn Bringert34251342009-12-15 13:33:11 +00001006 ComponentName cn = new ComponentName("com.android.alarmclock",
1007 "com.android.alarmclock.AnalogAppWidgetProvider");
Winson Chungb3302ae2012-05-01 10:19:14 -07001008 return addAppWidget(db, values, cn, 2, 2, null);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001009 }
Adam Cohen228da5a2011-07-27 22:23:47 -07001010
Winson Chungb3302ae2012-05-01 10:19:14 -07001011 private boolean addAppWidget(XmlResourceParser parser, AttributeSet attrs, int type,
1012 SQLiteDatabase db, ContentValues values, TypedArray a,
1013 PackageManager packageManager) throws XmlPullParserException, IOException {
Romain Guy693599f2010-03-23 10:58:18 -07001014
Mike Cleronb87bd162009-10-30 16:36:56 -07001015 String packageName = a.getString(R.styleable.Favorite_packageName);
1016 String className = a.getString(R.styleable.Favorite_className);
1017
1018 if (packageName == null || className == null) {
1019 return false;
1020 }
Romain Guy693599f2010-03-23 10:58:18 -07001021
1022 boolean hasPackage = true;
Mike Cleronb87bd162009-10-30 16:36:56 -07001023 ComponentName cn = new ComponentName(packageName, className);
Romain Guy693599f2010-03-23 10:58:18 -07001024 try {
1025 packageManager.getReceiverInfo(cn, 0);
1026 } catch (Exception e) {
1027 String[] packages = packageManager.currentToCanonicalPackageNames(
1028 new String[] { packageName });
1029 cn = new ComponentName(packages[0], className);
1030 try {
1031 packageManager.getReceiverInfo(cn, 0);
1032 } catch (Exception e1) {
1033 hasPackage = false;
1034 }
1035 }
1036
1037 if (hasPackage) {
1038 int spanX = a.getInt(R.styleable.Favorite_spanX, 0);
1039 int spanY = a.getInt(R.styleable.Favorite_spanY, 0);
Winson Chungb3302ae2012-05-01 10:19:14 -07001040
1041 // Read the extras
1042 Bundle extras = new Bundle();
1043 int widgetDepth = parser.getDepth();
1044 while ((type = parser.next()) != XmlPullParser.END_TAG ||
1045 parser.getDepth() > widgetDepth) {
1046 if (type != XmlPullParser.START_TAG) {
1047 continue;
1048 }
1049
1050 TypedArray ar = mContext.obtainStyledAttributes(attrs, R.styleable.Extra);
1051 if (TAG_EXTRA.equals(parser.getName())) {
1052 String key = ar.getString(R.styleable.Extra_key);
1053 String value = ar.getString(R.styleable.Extra_value);
1054 if (key != null && value != null) {
1055 extras.putString(key, value);
1056 } else {
1057 throw new RuntimeException("Widget extras must have a key and value");
1058 }
1059 } else {
1060 throw new RuntimeException("Widgets can contain only extras");
1061 }
1062 ar.recycle();
1063 }
1064
1065 return addAppWidget(db, values, cn, spanX, spanY, extras);
Romain Guy693599f2010-03-23 10:58:18 -07001066 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -07001067
Romain Guy693599f2010-03-23 10:58:18 -07001068 return false;
Bjorn Bringert7984c942009-12-09 15:38:25 +00001069 }
1070
1071 private boolean addAppWidget(SQLiteDatabase db, ContentValues values, ComponentName cn,
Winson Chungb3302ae2012-05-01 10:19:14 -07001072 int spanX, int spanY, Bundle extras) {
Mike Cleronb87bd162009-10-30 16:36:56 -07001073 boolean allocatedAppWidgets = false;
1074 final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext);
1075
1076 try {
1077 int appWidgetId = mAppWidgetHost.allocateAppWidgetId();
Daniel Lehmannc3a80402012-04-23 21:35:11 -07001078
Mike Cleronb87bd162009-10-30 16:36:56 -07001079 values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_APPWIDGET);
Bjorn Bringert7984c942009-12-09 15:38:25 +00001080 values.put(Favorites.SPANX, spanX);
1081 values.put(Favorites.SPANY, spanY);
Mike Cleronb87bd162009-10-30 16:36:56 -07001082 values.put(Favorites.APPWIDGET_ID, appWidgetId);
Michael Jurkaa8c760d2011-04-28 14:59:33 -07001083 values.put(Favorites._ID, generateNewId());
1084 dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values);
Mike Cleronb87bd162009-10-30 16:36:56 -07001085
1086 allocatedAppWidgets = true;
Daniel Lehmannc3a80402012-04-23 21:35:11 -07001087
Michael Jurka8b805b12012-04-18 14:23:14 -07001088 // TODO: need to check return value
1089 appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, cn);
Winson Chungb3302ae2012-05-01 10:19:14 -07001090
1091 // Send a broadcast to configure the widget
1092 if (extras != null && !extras.isEmpty()) {
1093 Intent intent = new Intent(ACTION_APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE);
1094 intent.setComponent(cn);
1095 intent.putExtras(extras);
1096 intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
1097 mContext.sendBroadcast(intent);
1098 }
Mike Cleronb87bd162009-10-30 16:36:56 -07001099 } catch (RuntimeException ex) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -08001100 Log.e(TAG, "Problem allocating appWidgetId", ex);
Mike Cleronb87bd162009-10-30 16:36:56 -07001101 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -07001102
Mike Cleronb87bd162009-10-30 16:36:56 -07001103 return allocatedAppWidgets;
1104 }
Adam Cohen228da5a2011-07-27 22:23:47 -07001105
1106 private long addUriShortcut(SQLiteDatabase db, ContentValues values,
Mike Cleronb87bd162009-10-30 16:36:56 -07001107 TypedArray a) {
1108 Resources r = mContext.getResources();
1109
1110 final int iconResId = a.getResourceId(R.styleable.Favorite_icon, 0);
1111 final int titleResId = a.getResourceId(R.styleable.Favorite_title, 0);
1112
Romain Guy7eb9e5e2009-12-02 20:10:07 -08001113 Intent intent;
Mike Cleronb87bd162009-10-30 16:36:56 -07001114 String uri = null;
1115 try {
1116 uri = a.getString(R.styleable.Favorite_uri);
1117 intent = Intent.parseUri(uri, 0);
1118 } catch (URISyntaxException e) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -08001119 Log.w(TAG, "Shortcut has malformed uri: " + uri);
Adam Cohen228da5a2011-07-27 22:23:47 -07001120 return -1; // Oh well
Mike Cleronb87bd162009-10-30 16:36:56 -07001121 }
1122
1123 if (iconResId == 0 || titleResId == 0) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -08001124 Log.w(TAG, "Shortcut is missing title or icon resource ID");
Adam Cohen228da5a2011-07-27 22:23:47 -07001125 return -1;
Mike Cleronb87bd162009-10-30 16:36:56 -07001126 }
1127
Adam Cohen228da5a2011-07-27 22:23:47 -07001128 long id = generateNewId();
Mike Cleronb87bd162009-10-30 16:36:56 -07001129 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1130 values.put(Favorites.INTENT, intent.toUri(0));
1131 values.put(Favorites.TITLE, r.getString(titleResId));
1132 values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_SHORTCUT);
1133 values.put(Favorites.SPANX, 1);
1134 values.put(Favorites.SPANY, 1);
1135 values.put(Favorites.ICON_TYPE, Favorites.ICON_TYPE_RESOURCE);
1136 values.put(Favorites.ICON_PACKAGE, mContext.getPackageName());
1137 values.put(Favorites.ICON_RESOURCE, r.getResourceName(iconResId));
Adam Cohen228da5a2011-07-27 22:23:47 -07001138 values.put(Favorites._ID, id);
Mike Cleronb87bd162009-10-30 16:36:56 -07001139
Adam Cohen228da5a2011-07-27 22:23:47 -07001140 if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values) < 0) {
1141 return -1;
1142 }
1143 return id;
Mike Cleronb87bd162009-10-30 16:36:56 -07001144 }
1145 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -07001146
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001147 /**
1148 * Build a query string that will match any row where the column matches
1149 * anything in the values list.
1150 */
1151 static String buildOrWhereString(String column, int[] values) {
1152 StringBuilder selectWhere = new StringBuilder();
1153 for (int i = values.length - 1; i >= 0; i--) {
1154 selectWhere.append(column).append("=").append(values[i]);
1155 if (i > 0) {
1156 selectWhere.append(" OR ");
1157 }
1158 }
1159 return selectWhere.toString();
1160 }
1161
1162 static class SqlArguments {
1163 public final String table;
1164 public final String where;
1165 public final String[] args;
1166
1167 SqlArguments(Uri url, String where, String[] args) {
1168 if (url.getPathSegments().size() == 1) {
1169 this.table = url.getPathSegments().get(0);
1170 this.where = where;
1171 this.args = args;
1172 } else if (url.getPathSegments().size() != 2) {
1173 throw new IllegalArgumentException("Invalid URI: " + url);
1174 } else if (!TextUtils.isEmpty(where)) {
1175 throw new UnsupportedOperationException("WHERE clause not supported: " + url);
1176 } else {
1177 this.table = url.getPathSegments().get(0);
Daniel Lehmannc3a80402012-04-23 21:35:11 -07001178 this.where = "_id=" + ContentUris.parseId(url);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001179 this.args = null;
1180 }
1181 }
1182
1183 SqlArguments(Uri url) {
1184 if (url.getPathSegments().size() == 1) {
1185 table = url.getPathSegments().get(0);
1186 where = null;
1187 args = null;
1188 } else {
1189 throw new IllegalArgumentException("Invalid URI: " + url);
1190 }
1191 }
1192 }
1193}