blob: 0720259f489c598d3cf8201cf274615964a6c90f [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";
The Android Open Source Project31dd5032009-03-03 19:32:27 -080077
Winson Chungb3302ae2012-05-01 10:19:14 -070078 private static final String ACTION_APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE =
79 "com.android.launcher.action.APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE";
80
Jeffrey Sharkey2bbcae12009-03-31 14:37:57 -070081 /**
Romain Guy73b979d2009-06-09 12:57:21 -070082 * {@link Uri} triggered at any registered {@link android.database.ContentObserver} when
Jeffrey Sharkey2bbcae12009-03-31 14:37:57 -070083 * {@link AppWidgetHost#deleteHost()} is called during database creation.
84 * Use this to recall {@link AppWidgetHost#startListening()} if needed.
85 */
86 static final Uri CONTENT_APPWIDGET_RESET_URI =
87 Uri.parse("content://" + AUTHORITY + "/appWidgetReset");
Daniel Lehmannc3a80402012-04-23 21:35:11 -070088
Michael Jurkaa8c760d2011-04-28 14:59:33 -070089 private DatabaseHelper mOpenHelper;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080090
91 @Override
92 public boolean onCreate() {
93 mOpenHelper = new DatabaseHelper(getContext());
Michael Jurkaa8c760d2011-04-28 14:59:33 -070094 ((LauncherApplication) getContext()).setLauncherProvider(this);
The Android Open Source Project31dd5032009-03-03 19:32:27 -080095 return true;
96 }
97
98 @Override
99 public String getType(Uri uri) {
100 SqlArguments args = new SqlArguments(uri, null, null);
101 if (TextUtils.isEmpty(args.where)) {
102 return "vnd.android.cursor.dir/" + args.table;
103 } else {
104 return "vnd.android.cursor.item/" + args.table;
105 }
106 }
107
108 @Override
109 public Cursor query(Uri uri, String[] projection, String selection,
110 String[] selectionArgs, String sortOrder) {
111
112 SqlArguments args = new SqlArguments(uri, selection, selectionArgs);
113 SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
114 qb.setTables(args.table);
115
Romain Guy73b979d2009-06-09 12:57:21 -0700116 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800117 Cursor result = qb.query(db, projection, args.where, args.args, null, null, sortOrder);
118 result.setNotificationUri(getContext().getContentResolver(), uri);
119
120 return result;
121 }
122
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700123 private static long dbInsertAndCheck(DatabaseHelper helper,
124 SQLiteDatabase db, String table, String nullColumnHack, ContentValues values) {
125 if (!values.containsKey(LauncherSettings.Favorites._ID)) {
126 throw new RuntimeException("Error: attempting to add item without specifying an id");
127 }
128 return db.insert(table, nullColumnHack, values);
129 }
130
Adam Cohen228da5a2011-07-27 22:23:47 -0700131 private static void deleteId(SQLiteDatabase db, long id) {
132 Uri uri = LauncherSettings.Favorites.getContentUri(id, false);
133 SqlArguments args = new SqlArguments(uri, null, null);
134 db.delete(args.table, args.where, args.args);
135 }
136
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800137 @Override
138 public Uri insert(Uri uri, ContentValues initialValues) {
139 SqlArguments args = new SqlArguments(uri);
140
141 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700142 final long rowId = dbInsertAndCheck(mOpenHelper, db, args.table, null, initialValues);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800143 if (rowId <= 0) return null;
144
145 uri = ContentUris.withAppendedId(uri, rowId);
146 sendNotify(uri);
147
148 return uri;
149 }
150
151 @Override
152 public int bulkInsert(Uri uri, ContentValues[] values) {
153 SqlArguments args = new SqlArguments(uri);
154
155 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
156 db.beginTransaction();
157 try {
158 int numValues = values.length;
159 for (int i = 0; i < numValues; i++) {
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700160 if (dbInsertAndCheck(mOpenHelper, db, args.table, null, values[i]) < 0) {
161 return 0;
162 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800163 }
164 db.setTransactionSuccessful();
165 } finally {
166 db.endTransaction();
167 }
168
169 sendNotify(uri);
170 return values.length;
171 }
172
173 @Override
174 public int delete(Uri uri, String selection, String[] selectionArgs) {
175 SqlArguments args = new SqlArguments(uri, selection, selectionArgs);
176
177 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
178 int count = db.delete(args.table, args.where, args.args);
179 if (count > 0) sendNotify(uri);
180
181 return count;
182 }
183
184 @Override
185 public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
186 SqlArguments args = new SqlArguments(uri, selection, selectionArgs);
187
188 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
189 int count = db.update(args.table, values, args.where, args.args);
190 if (count > 0) sendNotify(uri);
191
192 return count;
193 }
194
195 private void sendNotify(Uri uri) {
196 String notify = uri.getQueryParameter(PARAMETER_NOTIFY);
197 if (notify == null || "true".equals(notify)) {
198 getContext().getContentResolver().notifyChange(uri, null);
199 }
200 }
201
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700202 public long generateNewId() {
203 return mOpenHelper.generateNewId();
204 }
205
Brian Muramatsub6a4d982012-04-24 17:16:04 -0700206 synchronized public void loadDefaultFavoritesIfNecessary() {
Michael Jurkab85f8a42012-04-25 15:48:32 -0700207 String spKey = LauncherApplication.getSharedPreferencesKey();
208 SharedPreferences sp = getContext().getSharedPreferences(spKey, Context.MODE_PRIVATE);
209 if (sp.getBoolean(DB_CREATED_BUT_DEFAULT_WORKSPACE_NOT_LOADED, false)) {
210 // Populate favorites table with initial favorites
211 SharedPreferences.Editor editor = sp.edit();
212 editor.remove(DB_CREATED_BUT_DEFAULT_WORKSPACE_NOT_LOADED);
213 mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(), R.xml.default_workspace);
214 editor.commit();
215 }
216 }
217
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800218 private static class DatabaseHelper extends SQLiteOpenHelper {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800219 private static final String TAG_FAVORITES = "favorites";
220 private static final String TAG_FAVORITE = "favorite";
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700221 private static final String TAG_CLOCK = "clock";
222 private static final String TAG_SEARCH = "search";
Mike Cleronb87bd162009-10-30 16:36:56 -0700223 private static final String TAG_APPWIDGET = "appwidget";
224 private static final String TAG_SHORTCUT = "shortcut";
Adam Cohen228da5a2011-07-27 22:23:47 -0700225 private static final String TAG_FOLDER = "folder";
Winson Chungb3302ae2012-05-01 10:19:14 -0700226 private static final String TAG_EXTRA = "extra";
Winson Chung3d503fb2011-07-13 17:25:49 -0700227
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800228 private final Context mContext;
The Android Open Source Project7376fae2009-03-11 12:11:58 -0700229 private final AppWidgetHost mAppWidgetHost;
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700230 private long mMaxId = -1;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800231
232 DatabaseHelper(Context context) {
233 super(context, DATABASE_NAME, null, DATABASE_VERSION);
234 mContext = context;
The Android Open Source Project7376fae2009-03-11 12:11:58 -0700235 mAppWidgetHost = new AppWidgetHost(context, Launcher.APPWIDGET_HOST_ID);
Winson Chung3d503fb2011-07-13 17:25:49 -0700236
237 // In the case where neither onCreate nor onUpgrade gets called, we read the maxId from
238 // the DB here
239 if (mMaxId == -1) {
240 mMaxId = initializeMaxId(getWritableDatabase());
241 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800242 }
243
Jeffrey Sharkey2bbcae12009-03-31 14:37:57 -0700244 /**
245 * Send notification that we've deleted the {@link AppWidgetHost},
246 * probably as part of the initial database creation. The receiver may
247 * want to re-call {@link AppWidgetHost#startListening()} to ensure
248 * callbacks are correctly set.
249 */
250 private void sendAppWidgetResetNotify() {
251 final ContentResolver resolver = mContext.getContentResolver();
252 resolver.notifyChange(CONTENT_APPWIDGET_RESET_URI, null);
253 }
254
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800255 @Override
256 public void onCreate(SQLiteDatabase db) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800257 if (LOGD) Log.d(TAG, "creating new launcher database");
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700258
259 mMaxId = 1;
260
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800261 db.execSQL("CREATE TABLE favorites (" +
262 "_id INTEGER PRIMARY KEY," +
263 "title TEXT," +
264 "intent TEXT," +
265 "container INTEGER," +
266 "screen INTEGER," +
267 "cellX INTEGER," +
268 "cellY INTEGER," +
269 "spanX INTEGER," +
270 "spanY INTEGER," +
271 "itemType INTEGER," +
The Android Open Source Projectca9475f2009-03-13 13:04:24 -0700272 "appWidgetId INTEGER NOT NULL DEFAULT -1," +
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800273 "isShortcut INTEGER," +
274 "iconType INTEGER," +
275 "iconPackage TEXT," +
276 "iconResource TEXT," +
277 "icon BLOB," +
278 "uri TEXT," +
279 "displayMode INTEGER" +
280 ");");
281
The Android Open Source Project7376fae2009-03-11 12:11:58 -0700282 // Database was just created, so wipe any previous widgets
283 if (mAppWidgetHost != null) {
284 mAppWidgetHost.deleteHost();
Jeffrey Sharkey2bbcae12009-03-31 14:37:57 -0700285 sendAppWidgetResetNotify();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800286 }
Winson Chung3d503fb2011-07-13 17:25:49 -0700287
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800288 if (!convertDatabase(db)) {
Michael Jurkab85f8a42012-04-25 15:48:32 -0700289 // Set a shared pref so that we know we need to load the default workspace later
290 setFlagToLoadDefaultWorkspaceLater();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800291 }
292 }
293
Michael Jurkab85f8a42012-04-25 15:48:32 -0700294 private void setFlagToLoadDefaultWorkspaceLater() {
295 String spKey = LauncherApplication.getSharedPreferencesKey();
296 SharedPreferences sp = mContext.getSharedPreferences(spKey, Context.MODE_PRIVATE);
297 SharedPreferences.Editor editor = sp.edit();
298 editor.putBoolean(DB_CREATED_BUT_DEFAULT_WORKSPACE_NOT_LOADED, true);
299 editor.commit();
300 }
301
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800302 private boolean convertDatabase(SQLiteDatabase db) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800303 if (LOGD) Log.d(TAG, "converting database from an older format, but not onUpgrade");
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800304 boolean converted = false;
305
306 final Uri uri = Uri.parse("content://" + Settings.AUTHORITY +
307 "/old_favorites?notify=true");
308 final ContentResolver resolver = mContext.getContentResolver();
309 Cursor cursor = null;
310
311 try {
312 cursor = resolver.query(uri, null, null, null, null);
313 } catch (Exception e) {
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700314 // Ignore
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800315 }
316
317 // We already have a favorites database in the old provider
318 if (cursor != null && cursor.getCount() > 0) {
319 try {
320 converted = copyFromCursor(db, cursor) > 0;
321 } finally {
322 cursor.close();
323 }
324
325 if (converted) {
326 resolver.delete(uri, null, null);
327 }
328 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700329
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800330 if (converted) {
The Android Open Source Project7376fae2009-03-11 12:11:58 -0700331 // Convert widgets from this import into widgets
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800332 if (LOGD) Log.d(TAG, "converted and now triggering widget upgrade");
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800333 convertWidgets(db);
334 }
335
336 return converted;
337 }
338
339 private int copyFromCursor(SQLiteDatabase db, Cursor c) {
Romain Guy73b979d2009-06-09 12:57:21 -0700340 final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800341 final int intentIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.INTENT);
342 final int titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE);
343 final int iconTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_TYPE);
344 final int iconIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON);
345 final int iconPackageIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_PACKAGE);
346 final int iconResourceIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_RESOURCE);
347 final int containerIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER);
348 final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE);
349 final int screenIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN);
350 final int cellXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX);
351 final int cellYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY);
352 final int uriIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.URI);
353 final int displayModeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.DISPLAY_MODE);
354
355 ContentValues[] rows = new ContentValues[c.getCount()];
356 int i = 0;
357 while (c.moveToNext()) {
358 ContentValues values = new ContentValues(c.getColumnCount());
Romain Guy73b979d2009-06-09 12:57:21 -0700359 values.put(LauncherSettings.Favorites._ID, c.getLong(idIndex));
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800360 values.put(LauncherSettings.Favorites.INTENT, c.getString(intentIndex));
361 values.put(LauncherSettings.Favorites.TITLE, c.getString(titleIndex));
362 values.put(LauncherSettings.Favorites.ICON_TYPE, c.getInt(iconTypeIndex));
363 values.put(LauncherSettings.Favorites.ICON, c.getBlob(iconIndex));
364 values.put(LauncherSettings.Favorites.ICON_PACKAGE, c.getString(iconPackageIndex));
365 values.put(LauncherSettings.Favorites.ICON_RESOURCE, c.getString(iconResourceIndex));
366 values.put(LauncherSettings.Favorites.CONTAINER, c.getInt(containerIndex));
367 values.put(LauncherSettings.Favorites.ITEM_TYPE, c.getInt(itemTypeIndex));
The Android Open Source Project7376fae2009-03-11 12:11:58 -0700368 values.put(LauncherSettings.Favorites.APPWIDGET_ID, -1);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800369 values.put(LauncherSettings.Favorites.SCREEN, c.getInt(screenIndex));
370 values.put(LauncherSettings.Favorites.CELLX, c.getInt(cellXIndex));
371 values.put(LauncherSettings.Favorites.CELLY, c.getInt(cellYIndex));
372 values.put(LauncherSettings.Favorites.URI, c.getString(uriIndex));
373 values.put(LauncherSettings.Favorites.DISPLAY_MODE, c.getInt(displayModeIndex));
374 rows[i++] = values;
375 }
376
377 db.beginTransaction();
378 int total = 0;
379 try {
380 int numValues = rows.length;
381 for (i = 0; i < numValues; i++) {
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700382 if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, rows[i]) < 0) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800383 return 0;
384 } else {
385 total++;
386 }
387 }
388 db.setTransactionSuccessful();
389 } finally {
390 db.endTransaction();
391 }
392
393 return total;
394 }
395
396 @Override
397 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800398 if (LOGD) Log.d(TAG, "onUpgrade triggered");
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700399
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800400 int version = oldVersion;
The Android Open Source Projectca9475f2009-03-13 13:04:24 -0700401 if (version < 3) {
402 // upgrade 1,2 -> 3 added appWidgetId column
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800403 db.beginTransaction();
404 try {
The Android Open Source Project7376fae2009-03-11 12:11:58 -0700405 // Insert new column for holding appWidgetIds
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800406 db.execSQL("ALTER TABLE favorites " +
The Android Open Source Projectca9475f2009-03-13 13:04:24 -0700407 "ADD COLUMN appWidgetId INTEGER NOT NULL DEFAULT -1;");
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800408 db.setTransactionSuccessful();
The Android Open Source Projectca9475f2009-03-13 13:04:24 -0700409 version = 3;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800410 } catch (SQLException ex) {
411 // Old version remains, which means we wipe old data
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800412 Log.e(TAG, ex.getMessage(), ex);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800413 } finally {
414 db.endTransaction();
415 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700416
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800417 // Convert existing widgets only if table upgrade was successful
The Android Open Source Projectca9475f2009-03-13 13:04:24 -0700418 if (version == 3) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800419 convertWidgets(db);
420 }
421 }
Romain Guy73b979d2009-06-09 12:57:21 -0700422
423 if (version < 4) {
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800424 version = 4;
Romain Guy73b979d2009-06-09 12:57:21 -0700425 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700426
Romain Guy509cd6a2010-03-23 15:10:56 -0700427 // Where's version 5?
428 // - Donut and sholes on 2.0 shipped with version 4 of launcher1.
429 // - Passion shipped on 2.1 with version 6 of launcher2
430 // - Sholes shipped on 2.1r1 (aka Mr. 3) with version 5 of launcher 1
431 // but version 5 on there was the updateContactsShortcuts change
432 // which was version 6 in launcher 2 (first shipped on passion 2.1r1).
433 // The updateContactsShortcuts change is idempotent, so running it twice
434 // is okay so we'll do that when upgrading the devices that shipped with it.
435 if (version < 6) {
Mike Cleron3a2b3f22009-11-05 17:17:42 -0800436 // We went from 3 to 5 screens. Move everything 1 to the right
437 db.beginTransaction();
438 try {
439 db.execSQL("UPDATE favorites SET screen=(screen + 1);");
440 db.setTransactionSuccessful();
Mike Cleron3a2b3f22009-11-05 17:17:42 -0800441 } catch (SQLException ex) {
442 // Old version remains, which means we wipe old data
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800443 Log.e(TAG, ex.getMessage(), ex);
Mike Cleron3a2b3f22009-11-05 17:17:42 -0800444 } finally {
445 db.endTransaction();
446 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700447
Romain Guy509cd6a2010-03-23 15:10:56 -0700448 // We added the fast track.
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800449 if (updateContactsShortcuts(db)) {
450 version = 6;
451 }
452 }
Bjorn Bringert7984c942009-12-09 15:38:25 +0000453
454 if (version < 7) {
455 // Version 7 gets rid of the special search widget.
456 convertWidgets(db);
457 version = 7;
458 }
459
Joe Onorato0589f0f2010-02-08 13:44:00 -0800460 if (version < 8) {
461 // Version 8 (froyo) has the icons all normalized. This should
462 // already be the case in practice, but we now rely on it and don't
463 // resample the images each time.
464 normalizeIcons(db);
465 version = 8;
466 }
467
Winson Chung3d503fb2011-07-13 17:25:49 -0700468 if (version < 9) {
469 // The max id is not yet set at this point (onUpgrade is triggered in the ctor
470 // before it gets a change to get set, so we need to read it here when we use it)
471 if (mMaxId == -1) {
472 mMaxId = initializeMaxId(db);
473 }
474
475 // Add default hotseat icons
Winson Chung6d092682011-11-16 18:43:26 -0800476 loadFavorites(db, R.xml.update_workspace);
Winson Chung3d503fb2011-07-13 17:25:49 -0700477 version = 9;
478 }
479
Daniel Lehmannd02402c2012-05-14 18:30:53 -0700480 // We bumped the version three time during JB, once to update the launch flags, once to
481 // update the override for the default launch animation and once to set the mimetype
482 // to improve startup performance
483 if (version < 12) {
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700484 // Contact shortcuts need a different set of flags to be launched now
485 // The updateContactsShortcuts change is idempotent, so we can keep using it like
486 // back in the Donut days
487 updateContactsShortcuts(db);
Daniel Lehmannd02402c2012-05-14 18:30:53 -0700488 version = 12;
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700489 }
490
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800491 if (version != DATABASE_VERSION) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800492 Log.w(TAG, "Destroying all old data.");
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800493 db.execSQL("DROP TABLE IF EXISTS " + TABLE_FAVORITES);
494 onCreate(db);
495 }
496 }
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800497
498 private boolean updateContactsShortcuts(SQLiteDatabase db) {
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800499 final String selectWhere = buildOrWhereString(Favorites.ITEM_TYPE,
500 new int[] { Favorites.ITEM_TYPE_SHORTCUT });
501
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700502 Cursor c = null;
503 final String actionQuickContact = "com.android.contacts.action.QUICK_CONTACT";
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800504 db.beginTransaction();
505 try {
506 // Select and iterate through each matching widget
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700507 c = db.query(TABLE_FAVORITES,
508 new String[] { Favorites._ID, Favorites.INTENT },
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800509 selectWhere, null, null, null, null);
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700510 if (c == null) return false;
511
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800512 if (LOGD) Log.d(TAG, "found upgrade cursor count=" + c.getCount());
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700513
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800514 final int idIndex = c.getColumnIndex(Favorites._ID);
515 final int intentIndex = c.getColumnIndex(Favorites.INTENT);
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700516
517 while (c.moveToNext()) {
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800518 long favoriteId = c.getLong(idIndex);
519 final String intentUri = c.getString(intentIndex);
520 if (intentUri != null) {
521 try {
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700522 final Intent intent = Intent.parseUri(intentUri, 0);
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800523 android.util.Log.d("Home", intent.toString());
524 final Uri uri = intent.getData();
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700525 if (uri != null) {
526 final String data = uri.toString();
527 if ((Intent.ACTION_VIEW.equals(intent.getAction()) ||
528 actionQuickContact.equals(intent.getAction())) &&
529 (data.startsWith("content://contacts/people/") ||
530 data.startsWith("content://com.android.contacts/" +
531 "contacts/lookup/"))) {
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800532
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700533 final Intent newIntent = new Intent(actionQuickContact);
534 // When starting from the launcher, start in a new, cleared task
535 // CLEAR_WHEN_TASK_RESET cannot reset the root of a task, so we
536 // clear the whole thing preemptively here since
537 // QuickContactActivity will finish itself when launching other
538 // detail activities.
539 newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
540 Intent.FLAG_ACTIVITY_CLEAR_TASK);
Winson Chung2672ff92012-05-04 16:22:30 -0700541 newIntent.putExtra(
542 Launcher.INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION, true);
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700543 newIntent.setData(uri);
Daniel Lehmannd02402c2012-05-14 18:30:53 -0700544 // Determine the type and also put that in the shortcut
545 // (that can speed up launch a bit)
546 newIntent.setDataAndType(uri, newIntent.resolveType(mContext));
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800547
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700548 final ContentValues values = new ContentValues();
549 values.put(LauncherSettings.Favorites.INTENT,
550 newIntent.toUri(0));
551
552 String updateWhere = Favorites._ID + "=" + favoriteId;
553 db.update(TABLE_FAVORITES, values, updateWhere, null);
554 }
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800555 }
556 } catch (RuntimeException ex) {
557 Log.e(TAG, "Problem upgrading shortcut", ex);
558 } catch (URISyntaxException e) {
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700559 Log.e(TAG, "Problem upgrading shortcut", e);
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800560 }
561 }
562 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700563
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800564 db.setTransactionSuccessful();
565 } catch (SQLException ex) {
566 Log.w(TAG, "Problem while upgrading contacts", ex);
567 return false;
568 } finally {
569 db.endTransaction();
570 if (c != null) {
571 c.close();
572 }
573 }
574
575 return true;
576 }
577
Joe Onorato0589f0f2010-02-08 13:44:00 -0800578 private void normalizeIcons(SQLiteDatabase db) {
579 Log.d(TAG, "normalizing icons");
580
Joe Onorato346e1292010-02-18 10:34:24 -0500581 db.beginTransaction();
Joe Onorato0589f0f2010-02-08 13:44:00 -0800582 Cursor c = null;
Joe Onorato9690b392010-03-23 17:34:37 -0400583 SQLiteStatement update = null;
Joe Onorato0589f0f2010-02-08 13:44:00 -0800584 try {
585 boolean logged = false;
Joe Onorato9690b392010-03-23 17:34:37 -0400586 update = db.compileStatement("UPDATE favorites "
Jeff Hamiltoneaf77d62010-02-13 00:08:17 -0600587 + "SET icon=? WHERE _id=?");
Joe Onorato0589f0f2010-02-08 13:44:00 -0800588
589 c = db.rawQuery("SELECT _id, icon FROM favorites WHERE iconType=" +
590 Favorites.ICON_TYPE_BITMAP, null);
591
592 final int idIndex = c.getColumnIndexOrThrow(Favorites._ID);
593 final int iconIndex = c.getColumnIndexOrThrow(Favorites.ICON);
594
595 while (c.moveToNext()) {
596 long id = c.getLong(idIndex);
597 byte[] data = c.getBlob(iconIndex);
598 try {
599 Bitmap bitmap = Utilities.resampleIconBitmap(
600 BitmapFactory.decodeByteArray(data, 0, data.length),
601 mContext);
602 if (bitmap != null) {
603 update.bindLong(1, id);
604 data = ItemInfo.flattenBitmap(bitmap);
605 if (data != null) {
606 update.bindBlob(2, data);
607 update.execute();
608 }
609 bitmap.recycle();
Joe Onorato0589f0f2010-02-08 13:44:00 -0800610 }
611 } catch (Exception e) {
612 if (!logged) {
613 Log.e(TAG, "Failed normalizing icon " + id, e);
614 } else {
615 Log.e(TAG, "Also failed normalizing icon " + id);
616 }
617 logged = true;
618 }
619 }
Bjorn Bringert3a928e42010-02-19 11:15:40 +0000620 db.setTransactionSuccessful();
Joe Onorato0589f0f2010-02-08 13:44:00 -0800621 } catch (SQLException ex) {
622 Log.w(TAG, "Problem while allocating appWidgetIds for existing widgets", ex);
623 } finally {
624 db.endTransaction();
Joe Onorato9690b392010-03-23 17:34:37 -0400625 if (update != null) {
626 update.close();
627 }
Joe Onorato0589f0f2010-02-08 13:44:00 -0800628 if (c != null) {
629 c.close();
630 }
631 }
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700632 }
633
634 // Generates a new ID to use for an object in your database. This method should be only
635 // called from the main UI thread. As an exception, we do call it when we call the
636 // constructor from the worker thread; however, this doesn't extend until after the
637 // constructor is called, and we only pass a reference to LauncherProvider to LauncherApp
638 // after that point
639 public long generateNewId() {
640 if (mMaxId < 0) {
641 throw new RuntimeException("Error: max id was not initialized");
642 }
643 mMaxId += 1;
644 return mMaxId;
645 }
646
647 private long initializeMaxId(SQLiteDatabase db) {
648 Cursor c = db.rawQuery("SELECT MAX(_id) FROM favorites", null);
649
650 // get the result
651 final int maxIdIndex = 0;
652 long id = -1;
653 if (c != null && c.moveToNext()) {
654 id = c.getLong(maxIdIndex);
655 }
Michael Jurka5130e402011-10-13 04:55:35 -0700656 if (c != null) {
657 c.close();
658 }
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700659
660 if (id == -1) {
661 throw new RuntimeException("Error: could not query max id");
662 }
663
664 return id;
Joe Onorato0589f0f2010-02-08 13:44:00 -0800665 }
666
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800667 /**
The Android Open Source Project7376fae2009-03-11 12:11:58 -0700668 * Upgrade existing clock and photo frame widgets into their new widget
Bjorn Bringert93c45762009-12-16 13:19:47 +0000669 * equivalents.
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800670 */
671 private void convertWidgets(SQLiteDatabase db) {
Bjorn Bringert34251342009-12-15 13:33:11 +0000672 final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800673 final int[] bindSources = new int[] {
674 Favorites.ITEM_TYPE_WIDGET_CLOCK,
675 Favorites.ITEM_TYPE_WIDGET_PHOTO_FRAME,
Bjorn Bringert7984c942009-12-09 15:38:25 +0000676 Favorites.ITEM_TYPE_WIDGET_SEARCH,
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800677 };
Bjorn Bringert7984c942009-12-09 15:38:25 +0000678
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800679 final String selectWhere = buildOrWhereString(Favorites.ITEM_TYPE, bindSources);
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700680
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800681 Cursor c = null;
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700682
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800683 db.beginTransaction();
684 try {
685 // Select and iterate through each matching widget
Bjorn Bringert7984c942009-12-09 15:38:25 +0000686 c = db.query(TABLE_FAVORITES, new String[] { Favorites._ID, Favorites.ITEM_TYPE },
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800687 selectWhere, null, null, null, null);
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700688
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800689 if (LOGD) Log.d(TAG, "found upgrade cursor count=" + c.getCount());
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700690
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800691 final ContentValues values = new ContentValues();
692 while (c != null && c.moveToNext()) {
693 long favoriteId = c.getLong(0);
Bjorn Bringert7984c942009-12-09 15:38:25 +0000694 int favoriteType = c.getInt(1);
695
The Android Open Source Project7376fae2009-03-11 12:11:58 -0700696 // Allocate and update database with new appWidgetId
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800697 try {
The Android Open Source Project7376fae2009-03-11 12:11:58 -0700698 int appWidgetId = mAppWidgetHost.allocateAppWidgetId();
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700699
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800700 if (LOGD) {
701 Log.d(TAG, "allocated appWidgetId=" + appWidgetId
702 + " for favoriteId=" + favoriteId);
703 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800704 values.clear();
Bjorn Bringert7984c942009-12-09 15:38:25 +0000705 values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_APPWIDGET);
706 values.put(Favorites.APPWIDGET_ID, appWidgetId);
707
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800708 // Original widgets might not have valid spans when upgrading
Bjorn Bringert7984c942009-12-09 15:38:25 +0000709 if (favoriteType == Favorites.ITEM_TYPE_WIDGET_SEARCH) {
710 values.put(LauncherSettings.Favorites.SPANX, 4);
711 values.put(LauncherSettings.Favorites.SPANY, 1);
712 } else {
713 values.put(LauncherSettings.Favorites.SPANX, 2);
714 values.put(LauncherSettings.Favorites.SPANY, 2);
715 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800716
717 String updateWhere = Favorites._ID + "=" + favoriteId;
718 db.update(TABLE_FAVORITES, values, updateWhere, null);
Bjorn Bringert34251342009-12-15 13:33:11 +0000719
Bjorn Bringert34251342009-12-15 13:33:11 +0000720 if (favoriteType == Favorites.ITEM_TYPE_WIDGET_CLOCK) {
Michael Jurka8b805b12012-04-18 14:23:14 -0700721 // TODO: check return value
722 appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId,
Bjorn Bringert34251342009-12-15 13:33:11 +0000723 new ComponentName("com.android.alarmclock",
724 "com.android.alarmclock.AnalogAppWidgetProvider"));
725 } else if (favoriteType == Favorites.ITEM_TYPE_WIDGET_PHOTO_FRAME) {
Michael Jurka8b805b12012-04-18 14:23:14 -0700726 // TODO: check return value
727 appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId,
Bjorn Bringert34251342009-12-15 13:33:11 +0000728 new ComponentName("com.android.camera",
729 "com.android.camera.PhotoAppWidgetProvider"));
730 } else if (favoriteType == Favorites.ITEM_TYPE_WIDGET_SEARCH) {
Michael Jurka8b805b12012-04-18 14:23:14 -0700731 // TODO: check return value
732 appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId,
Bjorn Bringertcd8fec02010-01-14 13:26:43 +0000733 getSearchWidgetProvider());
Bjorn Bringert34251342009-12-15 13:33:11 +0000734 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800735 } catch (RuntimeException ex) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800736 Log.e(TAG, "Problem allocating appWidgetId", ex);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800737 }
738 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700739
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800740 db.setTransactionSuccessful();
741 } catch (SQLException ex) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800742 Log.w(TAG, "Problem while allocating appWidgetIds for existing widgets", ex);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800743 } finally {
744 db.endTransaction();
745 if (c != null) {
746 c.close();
747 }
748 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800749 }
750
Michael Jurka8b805b12012-04-18 14:23:14 -0700751 private static final void beginDocument(XmlPullParser parser, String firstElementName)
752 throws XmlPullParserException, IOException {
753 int type;
754 while ((type = parser.next()) != parser.START_TAG
755 && type != parser.END_DOCUMENT) {
756 ;
757 }
758
759 if (type != parser.START_TAG) {
760 throw new XmlPullParserException("No start tag found");
761 }
762
763 if (!parser.getName().equals(firstElementName)) {
764 throw new XmlPullParserException("Unexpected start tag: found " + parser.getName() +
765 ", expected " + firstElementName);
766 }
767 }
768
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800769 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800770 * Loads the default set of favorite packages from an xml file.
771 *
772 * @param db The database to write the values into
Winson Chung3d503fb2011-07-13 17:25:49 -0700773 * @param filterContainerId The specific container id of items to load
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800774 */
Winson Chung6d092682011-11-16 18:43:26 -0800775 private int loadFavorites(SQLiteDatabase db, int workspaceResourceId) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800776 Intent intent = new Intent(Intent.ACTION_MAIN, null);
777 intent.addCategory(Intent.CATEGORY_LAUNCHER);
778 ContentValues values = new ContentValues();
779
780 PackageManager packageManager = mContext.getPackageManager();
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800781 int allAppsButtonRank =
782 mContext.getResources().getInteger(R.integer.hotseat_all_apps_index);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800783 int i = 0;
784 try {
Winson Chung6d092682011-11-16 18:43:26 -0800785 XmlResourceParser parser = mContext.getResources().getXml(workspaceResourceId);
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700786 AttributeSet attrs = Xml.asAttributeSet(parser);
Michael Jurka8b805b12012-04-18 14:23:14 -0700787 beginDocument(parser, TAG_FAVORITES);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800788
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700789 final int depth = parser.getDepth();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800790
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700791 int type;
792 while (((type = parser.next()) != XmlPullParser.END_TAG ||
793 parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
794
795 if (type != XmlPullParser.START_TAG) {
796 continue;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800797 }
798
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700799 boolean added = false;
800 final String name = parser.getName();
801
802 TypedArray a = mContext.obtainStyledAttributes(attrs, R.styleable.Favorite);
803
Winson Chung3d503fb2011-07-13 17:25:49 -0700804 long container = LauncherSettings.Favorites.CONTAINER_DESKTOP;
805 if (a.hasValue(R.styleable.Favorite_container)) {
806 container = Long.valueOf(a.getString(R.styleable.Favorite_container));
807 }
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700808
Winson Chung6d092682011-11-16 18:43:26 -0800809 String screen = a.getString(R.styleable.Favorite_screen);
810 String x = a.getString(R.styleable.Favorite_x);
811 String y = a.getString(R.styleable.Favorite_y);
812
813 // If we are adding to the hotseat, the screen is used as the position in the
814 // hotseat. This screen can't be at position 0 because AllApps is in the
815 // zeroth position.
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800816 if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT
817 && Integer.valueOf(screen) == allAppsButtonRank) {
Winson Chung6d092682011-11-16 18:43:26 -0800818 throw new RuntimeException("Invalid screen position for hotseat item");
819 }
820
821 values.clear();
822 values.put(LauncherSettings.Favorites.CONTAINER, container);
823 values.put(LauncherSettings.Favorites.SCREEN, screen);
824 values.put(LauncherSettings.Favorites.CELLX, x);
825 values.put(LauncherSettings.Favorites.CELLY, y);
826
827 if (TAG_FAVORITE.equals(name)) {
828 long id = addAppShortcut(db, values, a, packageManager, intent);
829 added = id >= 0;
830 } else if (TAG_SEARCH.equals(name)) {
831 added = addSearchWidget(db, values);
832 } else if (TAG_CLOCK.equals(name)) {
833 added = addClockWidget(db, values);
834 } else if (TAG_APPWIDGET.equals(name)) {
Winson Chungb3302ae2012-05-01 10:19:14 -0700835 added = addAppWidget(parser, attrs, type, db, values, a, packageManager);
Winson Chung6d092682011-11-16 18:43:26 -0800836 } else if (TAG_SHORTCUT.equals(name)) {
837 long id = addUriShortcut(db, values, a);
838 added = id >= 0;
839 } else if (TAG_FOLDER.equals(name)) {
840 String title;
841 int titleResId = a.getResourceId(R.styleable.Favorite_title, -1);
842 if (titleResId != -1) {
843 title = mContext.getResources().getString(titleResId);
844 } else {
845 title = mContext.getResources().getString(R.string.folder_name);
Winson Chung3d503fb2011-07-13 17:25:49 -0700846 }
Winson Chung6d092682011-11-16 18:43:26 -0800847 values.put(LauncherSettings.Favorites.TITLE, title);
848 long folderId = addFolder(db, values);
849 added = folderId >= 0;
Winson Chung3d503fb2011-07-13 17:25:49 -0700850
Winson Chung6d092682011-11-16 18:43:26 -0800851 ArrayList<Long> folderItems = new ArrayList<Long>();
Winson Chung3d503fb2011-07-13 17:25:49 -0700852
Winson Chung6d092682011-11-16 18:43:26 -0800853 int folderDepth = parser.getDepth();
854 while ((type = parser.next()) != XmlPullParser.END_TAG ||
855 parser.getDepth() > folderDepth) {
856 if (type != XmlPullParser.START_TAG) {
857 continue;
858 }
859 final String folder_item_name = parser.getName();
860
861 TypedArray ar = mContext.obtainStyledAttributes(attrs,
862 R.styleable.Favorite);
863 values.clear();
864 values.put(LauncherSettings.Favorites.CONTAINER, folderId);
865
866 if (TAG_FAVORITE.equals(folder_item_name) && folderId >= 0) {
867 long id =
868 addAppShortcut(db, values, ar, packageManager, intent);
869 if (id >= 0) {
870 folderItems.add(id);
871 }
872 } else if (TAG_SHORTCUT.equals(folder_item_name) && folderId >= 0) {
873 long id = addUriShortcut(db, values, ar);
874 if (id >= 0) {
875 folderItems.add(id);
876 }
Adam Cohen228da5a2011-07-27 22:23:47 -0700877 } else {
Winson Chung6d092682011-11-16 18:43:26 -0800878 throw new RuntimeException("Folders can " +
879 "contain only shortcuts");
Adam Cohen228da5a2011-07-27 22:23:47 -0700880 }
Winson Chung6d092682011-11-16 18:43:26 -0800881 ar.recycle();
882 }
883 // We can only have folders with >= 2 items, so we need to remove the
884 // folder and clean up if less than 2 items were included, or some
885 // failed to add, and less than 2 were actually added
886 if (folderItems.size() < 2 && folderId >= 0) {
887 // We just delete the folder and any items that made it
888 deleteId(db, folderId);
889 if (folderItems.size() > 0) {
890 deleteId(db, folderItems.get(0));
Adam Cohen228da5a2011-07-27 22:23:47 -0700891 }
Winson Chung6d092682011-11-16 18:43:26 -0800892 added = false;
Winson Chung3d503fb2011-07-13 17:25:49 -0700893 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800894 }
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700895 if (added) i++;
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700896 a.recycle();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800897 }
898 } catch (XmlPullParserException e) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800899 Log.w(TAG, "Got exception parsing favorites.", e);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800900 } catch (IOException e) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800901 Log.w(TAG, "Got exception parsing favorites.", e);
Winson Chung3d503fb2011-07-13 17:25:49 -0700902 } catch (RuntimeException e) {
903 Log.w(TAG, "Got exception parsing favorites.", e);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800904 }
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700905
906 return i;
907 }
908
Adam Cohen228da5a2011-07-27 22:23:47 -0700909 private long addAppShortcut(SQLiteDatabase db, ContentValues values, TypedArray a,
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700910 PackageManager packageManager, Intent intent) {
Adam Cohen228da5a2011-07-27 22:23:47 -0700911 long id = -1;
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700912 ActivityInfo info;
913 String packageName = a.getString(R.styleable.Favorite_packageName);
914 String className = a.getString(R.styleable.Favorite_className);
915 try {
Romain Guy693599f2010-03-23 10:58:18 -0700916 ComponentName cn;
917 try {
918 cn = new ComponentName(packageName, className);
919 info = packageManager.getActivityInfo(cn, 0);
920 } catch (PackageManager.NameNotFoundException nnfe) {
921 String[] packages = packageManager.currentToCanonicalPackageNames(
922 new String[] { packageName });
923 cn = new ComponentName(packages[0], className);
924 info = packageManager.getActivityInfo(cn, 0);
925 }
Adam Cohen228da5a2011-07-27 22:23:47 -0700926 id = generateNewId();
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700927 intent.setComponent(cn);
Romain Guy693599f2010-03-23 10:58:18 -0700928 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
929 Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
Romain Guy1ce1a242009-06-23 17:34:54 -0700930 values.put(Favorites.INTENT, intent.toUri(0));
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700931 values.put(Favorites.TITLE, info.loadLabel(packageManager).toString());
932 values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_APPLICATION);
933 values.put(Favorites.SPANX, 1);
934 values.put(Favorites.SPANY, 1);
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700935 values.put(Favorites._ID, generateNewId());
Adam Cohen228da5a2011-07-27 22:23:47 -0700936 if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values) < 0) {
937 return -1;
938 }
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700939 } catch (PackageManager.NameNotFoundException e) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800940 Log.w(TAG, "Unable to add favorite: " + packageName +
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700941 "/" + className, e);
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700942 }
Adam Cohen228da5a2011-07-27 22:23:47 -0700943 return id;
944 }
945
946 private long addFolder(SQLiteDatabase db, ContentValues values) {
947 values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_FOLDER);
948 values.put(Favorites.SPANX, 1);
949 values.put(Favorites.SPANY, 1);
950 long id = generateNewId();
951 values.put(Favorites._ID, id);
952 if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values) <= 0) {
953 return -1;
954 } else {
955 return id;
956 }
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700957 }
958
Bjorn Bringertcd8fec02010-01-14 13:26:43 +0000959 private ComponentName getSearchWidgetProvider() {
960 SearchManager searchManager =
961 (SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE);
962 ComponentName searchComponent = searchManager.getGlobalSearchActivity();
963 if (searchComponent == null) return null;
964 return getProviderInPackage(searchComponent.getPackageName());
965 }
966
967 /**
968 * Gets an appwidget provider from the given package. If the package contains more than
969 * one appwidget provider, an arbitrary one is returned.
970 */
971 private ComponentName getProviderInPackage(String packageName) {
972 AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext);
973 List<AppWidgetProviderInfo> providers = appWidgetManager.getInstalledProviders();
974 if (providers == null) return null;
975 final int providerCount = providers.size();
976 for (int i = 0; i < providerCount; i++) {
977 ComponentName provider = providers.get(i).provider;
978 if (provider != null && provider.getPackageName().equals(packageName)) {
979 return provider;
980 }
981 }
982 return null;
983 }
984
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700985 private boolean addSearchWidget(SQLiteDatabase db, ContentValues values) {
Bjorn Bringertcd8fec02010-01-14 13:26:43 +0000986 ComponentName cn = getSearchWidgetProvider();
Winson Chungb3302ae2012-05-01 10:19:14 -0700987 return addAppWidget(db, values, cn, 4, 1, null);
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700988 }
989
990 private boolean addClockWidget(SQLiteDatabase db, ContentValues values) {
Bjorn Bringert34251342009-12-15 13:33:11 +0000991 ComponentName cn = new ComponentName("com.android.alarmclock",
992 "com.android.alarmclock.AnalogAppWidgetProvider");
Winson Chungb3302ae2012-05-01 10:19:14 -0700993 return addAppWidget(db, values, cn, 2, 2, null);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800994 }
Adam Cohen228da5a2011-07-27 22:23:47 -0700995
Winson Chungb3302ae2012-05-01 10:19:14 -0700996 private boolean addAppWidget(XmlResourceParser parser, AttributeSet attrs, int type,
997 SQLiteDatabase db, ContentValues values, TypedArray a,
998 PackageManager packageManager) throws XmlPullParserException, IOException {
Romain Guy693599f2010-03-23 10:58:18 -0700999
Mike Cleronb87bd162009-10-30 16:36:56 -07001000 String packageName = a.getString(R.styleable.Favorite_packageName);
1001 String className = a.getString(R.styleable.Favorite_className);
1002
1003 if (packageName == null || className == null) {
1004 return false;
1005 }
Romain Guy693599f2010-03-23 10:58:18 -07001006
1007 boolean hasPackage = true;
Mike Cleronb87bd162009-10-30 16:36:56 -07001008 ComponentName cn = new ComponentName(packageName, className);
Romain Guy693599f2010-03-23 10:58:18 -07001009 try {
1010 packageManager.getReceiverInfo(cn, 0);
1011 } catch (Exception e) {
1012 String[] packages = packageManager.currentToCanonicalPackageNames(
1013 new String[] { packageName });
1014 cn = new ComponentName(packages[0], className);
1015 try {
1016 packageManager.getReceiverInfo(cn, 0);
1017 } catch (Exception e1) {
1018 hasPackage = false;
1019 }
1020 }
1021
1022 if (hasPackage) {
1023 int spanX = a.getInt(R.styleable.Favorite_spanX, 0);
1024 int spanY = a.getInt(R.styleable.Favorite_spanY, 0);
Winson Chungb3302ae2012-05-01 10:19:14 -07001025
1026 // Read the extras
1027 Bundle extras = new Bundle();
1028 int widgetDepth = parser.getDepth();
1029 while ((type = parser.next()) != XmlPullParser.END_TAG ||
1030 parser.getDepth() > widgetDepth) {
1031 if (type != XmlPullParser.START_TAG) {
1032 continue;
1033 }
1034
1035 TypedArray ar = mContext.obtainStyledAttributes(attrs, R.styleable.Extra);
1036 if (TAG_EXTRA.equals(parser.getName())) {
1037 String key = ar.getString(R.styleable.Extra_key);
1038 String value = ar.getString(R.styleable.Extra_value);
1039 if (key != null && value != null) {
1040 extras.putString(key, value);
1041 } else {
1042 throw new RuntimeException("Widget extras must have a key and value");
1043 }
1044 } else {
1045 throw new RuntimeException("Widgets can contain only extras");
1046 }
1047 ar.recycle();
1048 }
1049
1050 return addAppWidget(db, values, cn, spanX, spanY, extras);
Romain Guy693599f2010-03-23 10:58:18 -07001051 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -07001052
Romain Guy693599f2010-03-23 10:58:18 -07001053 return false;
Bjorn Bringert7984c942009-12-09 15:38:25 +00001054 }
1055
1056 private boolean addAppWidget(SQLiteDatabase db, ContentValues values, ComponentName cn,
Winson Chungb3302ae2012-05-01 10:19:14 -07001057 int spanX, int spanY, Bundle extras) {
Mike Cleronb87bd162009-10-30 16:36:56 -07001058 boolean allocatedAppWidgets = false;
1059 final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext);
1060
1061 try {
1062 int appWidgetId = mAppWidgetHost.allocateAppWidgetId();
Daniel Lehmannc3a80402012-04-23 21:35:11 -07001063
Mike Cleronb87bd162009-10-30 16:36:56 -07001064 values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_APPWIDGET);
Bjorn Bringert7984c942009-12-09 15:38:25 +00001065 values.put(Favorites.SPANX, spanX);
1066 values.put(Favorites.SPANY, spanY);
Mike Cleronb87bd162009-10-30 16:36:56 -07001067 values.put(Favorites.APPWIDGET_ID, appWidgetId);
Michael Jurkaa8c760d2011-04-28 14:59:33 -07001068 values.put(Favorites._ID, generateNewId());
1069 dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values);
Mike Cleronb87bd162009-10-30 16:36:56 -07001070
1071 allocatedAppWidgets = true;
Daniel Lehmannc3a80402012-04-23 21:35:11 -07001072
Michael Jurka8b805b12012-04-18 14:23:14 -07001073 // TODO: need to check return value
1074 appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, cn);
Winson Chungb3302ae2012-05-01 10:19:14 -07001075
1076 // Send a broadcast to configure the widget
1077 if (extras != null && !extras.isEmpty()) {
1078 Intent intent = new Intent(ACTION_APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE);
1079 intent.setComponent(cn);
1080 intent.putExtras(extras);
1081 intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
1082 mContext.sendBroadcast(intent);
1083 }
Mike Cleronb87bd162009-10-30 16:36:56 -07001084 } catch (RuntimeException ex) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -08001085 Log.e(TAG, "Problem allocating appWidgetId", ex);
Mike Cleronb87bd162009-10-30 16:36:56 -07001086 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -07001087
Mike Cleronb87bd162009-10-30 16:36:56 -07001088 return allocatedAppWidgets;
1089 }
Adam Cohen228da5a2011-07-27 22:23:47 -07001090
1091 private long addUriShortcut(SQLiteDatabase db, ContentValues values,
Mike Cleronb87bd162009-10-30 16:36:56 -07001092 TypedArray a) {
1093 Resources r = mContext.getResources();
1094
1095 final int iconResId = a.getResourceId(R.styleable.Favorite_icon, 0);
1096 final int titleResId = a.getResourceId(R.styleable.Favorite_title, 0);
1097
Romain Guy7eb9e5e2009-12-02 20:10:07 -08001098 Intent intent;
Mike Cleronb87bd162009-10-30 16:36:56 -07001099 String uri = null;
1100 try {
1101 uri = a.getString(R.styleable.Favorite_uri);
1102 intent = Intent.parseUri(uri, 0);
1103 } catch (URISyntaxException e) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -08001104 Log.w(TAG, "Shortcut has malformed uri: " + uri);
Adam Cohen228da5a2011-07-27 22:23:47 -07001105 return -1; // Oh well
Mike Cleronb87bd162009-10-30 16:36:56 -07001106 }
1107
1108 if (iconResId == 0 || titleResId == 0) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -08001109 Log.w(TAG, "Shortcut is missing title or icon resource ID");
Adam Cohen228da5a2011-07-27 22:23:47 -07001110 return -1;
Mike Cleronb87bd162009-10-30 16:36:56 -07001111 }
1112
Adam Cohen228da5a2011-07-27 22:23:47 -07001113 long id = generateNewId();
Mike Cleronb87bd162009-10-30 16:36:56 -07001114 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1115 values.put(Favorites.INTENT, intent.toUri(0));
1116 values.put(Favorites.TITLE, r.getString(titleResId));
1117 values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_SHORTCUT);
1118 values.put(Favorites.SPANX, 1);
1119 values.put(Favorites.SPANY, 1);
1120 values.put(Favorites.ICON_TYPE, Favorites.ICON_TYPE_RESOURCE);
1121 values.put(Favorites.ICON_PACKAGE, mContext.getPackageName());
1122 values.put(Favorites.ICON_RESOURCE, r.getResourceName(iconResId));
Adam Cohen228da5a2011-07-27 22:23:47 -07001123 values.put(Favorites._ID, id);
Mike Cleronb87bd162009-10-30 16:36:56 -07001124
Adam Cohen228da5a2011-07-27 22:23:47 -07001125 if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values) < 0) {
1126 return -1;
1127 }
1128 return id;
Mike Cleronb87bd162009-10-30 16:36:56 -07001129 }
1130 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -07001131
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001132 /**
1133 * Build a query string that will match any row where the column matches
1134 * anything in the values list.
1135 */
1136 static String buildOrWhereString(String column, int[] values) {
1137 StringBuilder selectWhere = new StringBuilder();
1138 for (int i = values.length - 1; i >= 0; i--) {
1139 selectWhere.append(column).append("=").append(values[i]);
1140 if (i > 0) {
1141 selectWhere.append(" OR ");
1142 }
1143 }
1144 return selectWhere.toString();
1145 }
1146
1147 static class SqlArguments {
1148 public final String table;
1149 public final String where;
1150 public final String[] args;
1151
1152 SqlArguments(Uri url, String where, String[] args) {
1153 if (url.getPathSegments().size() == 1) {
1154 this.table = url.getPathSegments().get(0);
1155 this.where = where;
1156 this.args = args;
1157 } else if (url.getPathSegments().size() != 2) {
1158 throw new IllegalArgumentException("Invalid URI: " + url);
1159 } else if (!TextUtils.isEmpty(where)) {
1160 throw new UnsupportedOperationException("WHERE clause not supported: " + url);
1161 } else {
1162 this.table = url.getPathSegments().get(0);
Daniel Lehmannc3a80402012-04-23 21:35:11 -07001163 this.where = "_id=" + ContentUris.parseId(url);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001164 this.args = null;
1165 }
1166 }
1167
1168 SqlArguments(Uri url) {
1169 if (url.getPathSegments().size() == 1) {
1170 table = url.getPathSegments().get(0);
1171 where = null;
1172 args = null;
1173 } else {
1174 throw new IllegalArgumentException("Invalid URI: " + url);
1175 }
1176 }
1177 }
1178}