blob: 74cf7a43fdc2db74810f6942b59b6689df3b5d1a [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 Muramatsu5524b492012-10-02 16:55:54 -0700206 /**
207 * @param workspaceResId that can be 0 to use default or non-zero for specific resource
208 */
209 synchronized public void loadDefaultFavoritesIfNecessary(int workspaceResId) {
Michael Jurkab85f8a42012-04-25 15:48:32 -0700210 String spKey = LauncherApplication.getSharedPreferencesKey();
211 SharedPreferences sp = getContext().getSharedPreferences(spKey, Context.MODE_PRIVATE);
212 if (sp.getBoolean(DB_CREATED_BUT_DEFAULT_WORKSPACE_NOT_LOADED, false)) {
Brian Muramatsu5524b492012-10-02 16:55:54 -0700213 // Use default workspace resource if none provided
214 if (workspaceResId == 0) {
215 workspaceResId = R.xml.default_workspace;
216 }
217
Michael Jurkab85f8a42012-04-25 15:48:32 -0700218 // Populate favorites table with initial favorites
219 SharedPreferences.Editor editor = sp.edit();
220 editor.remove(DB_CREATED_BUT_DEFAULT_WORKSPACE_NOT_LOADED);
Brian Muramatsu5524b492012-10-02 16:55:54 -0700221 mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(), workspaceResId);
Michael Jurkab85f8a42012-04-25 15:48:32 -0700222 editor.commit();
223 }
224 }
225
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800226 private static class DatabaseHelper extends SQLiteOpenHelper {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800227 private static final String TAG_FAVORITES = "favorites";
228 private static final String TAG_FAVORITE = "favorite";
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700229 private static final String TAG_CLOCK = "clock";
230 private static final String TAG_SEARCH = "search";
Mike Cleronb87bd162009-10-30 16:36:56 -0700231 private static final String TAG_APPWIDGET = "appwidget";
232 private static final String TAG_SHORTCUT = "shortcut";
Adam Cohen228da5a2011-07-27 22:23:47 -0700233 private static final String TAG_FOLDER = "folder";
Winson Chungb3302ae2012-05-01 10:19:14 -0700234 private static final String TAG_EXTRA = "extra";
Winson Chung3d503fb2011-07-13 17:25:49 -0700235
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800236 private final Context mContext;
The Android Open Source Project7376fae2009-03-11 12:11:58 -0700237 private final AppWidgetHost mAppWidgetHost;
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700238 private long mMaxId = -1;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800239
240 DatabaseHelper(Context context) {
241 super(context, DATABASE_NAME, null, DATABASE_VERSION);
242 mContext = context;
The Android Open Source Project7376fae2009-03-11 12:11:58 -0700243 mAppWidgetHost = new AppWidgetHost(context, Launcher.APPWIDGET_HOST_ID);
Winson Chung3d503fb2011-07-13 17:25:49 -0700244
245 // In the case where neither onCreate nor onUpgrade gets called, we read the maxId from
246 // the DB here
247 if (mMaxId == -1) {
248 mMaxId = initializeMaxId(getWritableDatabase());
249 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800250 }
251
Jeffrey Sharkey2bbcae12009-03-31 14:37:57 -0700252 /**
253 * Send notification that we've deleted the {@link AppWidgetHost},
254 * probably as part of the initial database creation. The receiver may
255 * want to re-call {@link AppWidgetHost#startListening()} to ensure
256 * callbacks are correctly set.
257 */
258 private void sendAppWidgetResetNotify() {
259 final ContentResolver resolver = mContext.getContentResolver();
260 resolver.notifyChange(CONTENT_APPWIDGET_RESET_URI, null);
261 }
262
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800263 @Override
264 public void onCreate(SQLiteDatabase db) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800265 if (LOGD) Log.d(TAG, "creating new launcher database");
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700266
267 mMaxId = 1;
268
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800269 db.execSQL("CREATE TABLE favorites (" +
270 "_id INTEGER PRIMARY KEY," +
271 "title TEXT," +
272 "intent TEXT," +
273 "container INTEGER," +
274 "screen INTEGER," +
275 "cellX INTEGER," +
276 "cellY INTEGER," +
277 "spanX INTEGER," +
278 "spanY INTEGER," +
279 "itemType INTEGER," +
The Android Open Source Projectca9475f2009-03-13 13:04:24 -0700280 "appWidgetId INTEGER NOT NULL DEFAULT -1," +
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800281 "isShortcut INTEGER," +
282 "iconType INTEGER," +
283 "iconPackage TEXT," +
284 "iconResource TEXT," +
285 "icon BLOB," +
286 "uri TEXT," +
287 "displayMode INTEGER" +
288 ");");
289
The Android Open Source Project7376fae2009-03-11 12:11:58 -0700290 // Database was just created, so wipe any previous widgets
291 if (mAppWidgetHost != null) {
292 mAppWidgetHost.deleteHost();
Jeffrey Sharkey2bbcae12009-03-31 14:37:57 -0700293 sendAppWidgetResetNotify();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800294 }
Winson Chung3d503fb2011-07-13 17:25:49 -0700295
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800296 if (!convertDatabase(db)) {
Michael Jurkab85f8a42012-04-25 15:48:32 -0700297 // Set a shared pref so that we know we need to load the default workspace later
298 setFlagToLoadDefaultWorkspaceLater();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800299 }
300 }
301
Michael Jurkab85f8a42012-04-25 15:48:32 -0700302 private void setFlagToLoadDefaultWorkspaceLater() {
303 String spKey = LauncherApplication.getSharedPreferencesKey();
304 SharedPreferences sp = mContext.getSharedPreferences(spKey, Context.MODE_PRIVATE);
305 SharedPreferences.Editor editor = sp.edit();
306 editor.putBoolean(DB_CREATED_BUT_DEFAULT_WORKSPACE_NOT_LOADED, true);
307 editor.commit();
308 }
309
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800310 private boolean convertDatabase(SQLiteDatabase db) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800311 if (LOGD) Log.d(TAG, "converting database from an older format, but not onUpgrade");
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800312 boolean converted = false;
313
314 final Uri uri = Uri.parse("content://" + Settings.AUTHORITY +
315 "/old_favorites?notify=true");
316 final ContentResolver resolver = mContext.getContentResolver();
317 Cursor cursor = null;
318
319 try {
320 cursor = resolver.query(uri, null, null, null, null);
321 } catch (Exception e) {
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700322 // Ignore
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800323 }
324
325 // We already have a favorites database in the old provider
326 if (cursor != null && cursor.getCount() > 0) {
327 try {
328 converted = copyFromCursor(db, cursor) > 0;
329 } finally {
330 cursor.close();
331 }
332
333 if (converted) {
334 resolver.delete(uri, null, null);
335 }
336 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700337
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800338 if (converted) {
The Android Open Source Project7376fae2009-03-11 12:11:58 -0700339 // Convert widgets from this import into widgets
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800340 if (LOGD) Log.d(TAG, "converted and now triggering widget upgrade");
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800341 convertWidgets(db);
342 }
343
344 return converted;
345 }
346
347 private int copyFromCursor(SQLiteDatabase db, Cursor c) {
Romain Guy73b979d2009-06-09 12:57:21 -0700348 final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800349 final int intentIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.INTENT);
350 final int titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE);
351 final int iconTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_TYPE);
352 final int iconIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON);
353 final int iconPackageIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_PACKAGE);
354 final int iconResourceIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_RESOURCE);
355 final int containerIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER);
356 final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE);
357 final int screenIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN);
358 final int cellXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX);
359 final int cellYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY);
360 final int uriIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.URI);
361 final int displayModeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.DISPLAY_MODE);
362
363 ContentValues[] rows = new ContentValues[c.getCount()];
364 int i = 0;
365 while (c.moveToNext()) {
366 ContentValues values = new ContentValues(c.getColumnCount());
Romain Guy73b979d2009-06-09 12:57:21 -0700367 values.put(LauncherSettings.Favorites._ID, c.getLong(idIndex));
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800368 values.put(LauncherSettings.Favorites.INTENT, c.getString(intentIndex));
369 values.put(LauncherSettings.Favorites.TITLE, c.getString(titleIndex));
370 values.put(LauncherSettings.Favorites.ICON_TYPE, c.getInt(iconTypeIndex));
371 values.put(LauncherSettings.Favorites.ICON, c.getBlob(iconIndex));
372 values.put(LauncherSettings.Favorites.ICON_PACKAGE, c.getString(iconPackageIndex));
373 values.put(LauncherSettings.Favorites.ICON_RESOURCE, c.getString(iconResourceIndex));
374 values.put(LauncherSettings.Favorites.CONTAINER, c.getInt(containerIndex));
375 values.put(LauncherSettings.Favorites.ITEM_TYPE, c.getInt(itemTypeIndex));
The Android Open Source Project7376fae2009-03-11 12:11:58 -0700376 values.put(LauncherSettings.Favorites.APPWIDGET_ID, -1);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800377 values.put(LauncherSettings.Favorites.SCREEN, c.getInt(screenIndex));
378 values.put(LauncherSettings.Favorites.CELLX, c.getInt(cellXIndex));
379 values.put(LauncherSettings.Favorites.CELLY, c.getInt(cellYIndex));
380 values.put(LauncherSettings.Favorites.URI, c.getString(uriIndex));
381 values.put(LauncherSettings.Favorites.DISPLAY_MODE, c.getInt(displayModeIndex));
382 rows[i++] = values;
383 }
384
385 db.beginTransaction();
386 int total = 0;
387 try {
388 int numValues = rows.length;
389 for (i = 0; i < numValues; i++) {
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700390 if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, rows[i]) < 0) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800391 return 0;
392 } else {
393 total++;
394 }
395 }
396 db.setTransactionSuccessful();
397 } finally {
398 db.endTransaction();
399 }
400
401 return total;
402 }
403
404 @Override
405 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800406 if (LOGD) Log.d(TAG, "onUpgrade triggered");
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700407
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800408 int version = oldVersion;
The Android Open Source Projectca9475f2009-03-13 13:04:24 -0700409 if (version < 3) {
410 // upgrade 1,2 -> 3 added appWidgetId column
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800411 db.beginTransaction();
412 try {
The Android Open Source Project7376fae2009-03-11 12:11:58 -0700413 // Insert new column for holding appWidgetIds
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800414 db.execSQL("ALTER TABLE favorites " +
The Android Open Source Projectca9475f2009-03-13 13:04:24 -0700415 "ADD COLUMN appWidgetId INTEGER NOT NULL DEFAULT -1;");
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800416 db.setTransactionSuccessful();
The Android Open Source Projectca9475f2009-03-13 13:04:24 -0700417 version = 3;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800418 } catch (SQLException ex) {
419 // Old version remains, which means we wipe old data
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800420 Log.e(TAG, ex.getMessage(), ex);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800421 } finally {
422 db.endTransaction();
423 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700424
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800425 // Convert existing widgets only if table upgrade was successful
The Android Open Source Projectca9475f2009-03-13 13:04:24 -0700426 if (version == 3) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800427 convertWidgets(db);
428 }
429 }
Romain Guy73b979d2009-06-09 12:57:21 -0700430
431 if (version < 4) {
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800432 version = 4;
Romain Guy73b979d2009-06-09 12:57:21 -0700433 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700434
Romain Guy509cd6a2010-03-23 15:10:56 -0700435 // Where's version 5?
436 // - Donut and sholes on 2.0 shipped with version 4 of launcher1.
437 // - Passion shipped on 2.1 with version 6 of launcher2
438 // - Sholes shipped on 2.1r1 (aka Mr. 3) with version 5 of launcher 1
439 // but version 5 on there was the updateContactsShortcuts change
440 // which was version 6 in launcher 2 (first shipped on passion 2.1r1).
441 // The updateContactsShortcuts change is idempotent, so running it twice
442 // is okay so we'll do that when upgrading the devices that shipped with it.
443 if (version < 6) {
Mike Cleron3a2b3f22009-11-05 17:17:42 -0800444 // We went from 3 to 5 screens. Move everything 1 to the right
445 db.beginTransaction();
446 try {
447 db.execSQL("UPDATE favorites SET screen=(screen + 1);");
448 db.setTransactionSuccessful();
Mike Cleron3a2b3f22009-11-05 17:17:42 -0800449 } catch (SQLException ex) {
450 // Old version remains, which means we wipe old data
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800451 Log.e(TAG, ex.getMessage(), ex);
Mike Cleron3a2b3f22009-11-05 17:17:42 -0800452 } finally {
453 db.endTransaction();
454 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700455
Romain Guy509cd6a2010-03-23 15:10:56 -0700456 // We added the fast track.
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800457 if (updateContactsShortcuts(db)) {
458 version = 6;
459 }
460 }
Bjorn Bringert7984c942009-12-09 15:38:25 +0000461
462 if (version < 7) {
463 // Version 7 gets rid of the special search widget.
464 convertWidgets(db);
465 version = 7;
466 }
467
Joe Onorato0589f0f2010-02-08 13:44:00 -0800468 if (version < 8) {
469 // Version 8 (froyo) has the icons all normalized. This should
470 // already be the case in practice, but we now rely on it and don't
471 // resample the images each time.
472 normalizeIcons(db);
473 version = 8;
474 }
475
Winson Chung3d503fb2011-07-13 17:25:49 -0700476 if (version < 9) {
477 // The max id is not yet set at this point (onUpgrade is triggered in the ctor
478 // before it gets a change to get set, so we need to read it here when we use it)
479 if (mMaxId == -1) {
480 mMaxId = initializeMaxId(db);
481 }
482
483 // Add default hotseat icons
Winson Chung6d092682011-11-16 18:43:26 -0800484 loadFavorites(db, R.xml.update_workspace);
Winson Chung3d503fb2011-07-13 17:25:49 -0700485 version = 9;
486 }
487
Daniel Lehmannd02402c2012-05-14 18:30:53 -0700488 // We bumped the version three time during JB, once to update the launch flags, once to
489 // update the override for the default launch animation and once to set the mimetype
490 // to improve startup performance
491 if (version < 12) {
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700492 // Contact shortcuts need a different set of flags to be launched now
493 // The updateContactsShortcuts change is idempotent, so we can keep using it like
494 // back in the Donut days
495 updateContactsShortcuts(db);
Daniel Lehmannd02402c2012-05-14 18:30:53 -0700496 version = 12;
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700497 }
498
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800499 if (version != DATABASE_VERSION) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800500 Log.w(TAG, "Destroying all old data.");
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800501 db.execSQL("DROP TABLE IF EXISTS " + TABLE_FAVORITES);
502 onCreate(db);
503 }
504 }
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800505
506 private boolean updateContactsShortcuts(SQLiteDatabase db) {
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800507 final String selectWhere = buildOrWhereString(Favorites.ITEM_TYPE,
508 new int[] { Favorites.ITEM_TYPE_SHORTCUT });
509
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700510 Cursor c = null;
511 final String actionQuickContact = "com.android.contacts.action.QUICK_CONTACT";
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800512 db.beginTransaction();
513 try {
514 // Select and iterate through each matching widget
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700515 c = db.query(TABLE_FAVORITES,
516 new String[] { Favorites._ID, Favorites.INTENT },
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800517 selectWhere, null, null, null, null);
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700518 if (c == null) return false;
519
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800520 if (LOGD) Log.d(TAG, "found upgrade cursor count=" + c.getCount());
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700521
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800522 final int idIndex = c.getColumnIndex(Favorites._ID);
523 final int intentIndex = c.getColumnIndex(Favorites.INTENT);
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700524
525 while (c.moveToNext()) {
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800526 long favoriteId = c.getLong(idIndex);
527 final String intentUri = c.getString(intentIndex);
528 if (intentUri != null) {
529 try {
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700530 final Intent intent = Intent.parseUri(intentUri, 0);
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800531 android.util.Log.d("Home", intent.toString());
532 final Uri uri = intent.getData();
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700533 if (uri != null) {
534 final String data = uri.toString();
535 if ((Intent.ACTION_VIEW.equals(intent.getAction()) ||
536 actionQuickContact.equals(intent.getAction())) &&
537 (data.startsWith("content://contacts/people/") ||
538 data.startsWith("content://com.android.contacts/" +
539 "contacts/lookup/"))) {
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800540
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700541 final Intent newIntent = new Intent(actionQuickContact);
542 // When starting from the launcher, start in a new, cleared task
543 // CLEAR_WHEN_TASK_RESET cannot reset the root of a task, so we
544 // clear the whole thing preemptively here since
545 // QuickContactActivity will finish itself when launching other
546 // detail activities.
547 newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
548 Intent.FLAG_ACTIVITY_CLEAR_TASK);
Winson Chung2672ff92012-05-04 16:22:30 -0700549 newIntent.putExtra(
550 Launcher.INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION, true);
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700551 newIntent.setData(uri);
Daniel Lehmannd02402c2012-05-14 18:30:53 -0700552 // Determine the type and also put that in the shortcut
553 // (that can speed up launch a bit)
554 newIntent.setDataAndType(uri, newIntent.resolveType(mContext));
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800555
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700556 final ContentValues values = new ContentValues();
557 values.put(LauncherSettings.Favorites.INTENT,
558 newIntent.toUri(0));
559
560 String updateWhere = Favorites._ID + "=" + favoriteId;
561 db.update(TABLE_FAVORITES, values, updateWhere, null);
562 }
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800563 }
564 } catch (RuntimeException ex) {
565 Log.e(TAG, "Problem upgrading shortcut", ex);
566 } catch (URISyntaxException e) {
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700567 Log.e(TAG, "Problem upgrading shortcut", e);
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800568 }
569 }
570 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700571
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800572 db.setTransactionSuccessful();
573 } catch (SQLException ex) {
574 Log.w(TAG, "Problem while upgrading contacts", ex);
575 return false;
576 } finally {
577 db.endTransaction();
578 if (c != null) {
579 c.close();
580 }
581 }
582
583 return true;
584 }
585
Joe Onorato0589f0f2010-02-08 13:44:00 -0800586 private void normalizeIcons(SQLiteDatabase db) {
587 Log.d(TAG, "normalizing icons");
588
Joe Onorato346e1292010-02-18 10:34:24 -0500589 db.beginTransaction();
Joe Onorato0589f0f2010-02-08 13:44:00 -0800590 Cursor c = null;
Joe Onorato9690b392010-03-23 17:34:37 -0400591 SQLiteStatement update = null;
Joe Onorato0589f0f2010-02-08 13:44:00 -0800592 try {
593 boolean logged = false;
Joe Onorato9690b392010-03-23 17:34:37 -0400594 update = db.compileStatement("UPDATE favorites "
Jeff Hamiltoneaf77d62010-02-13 00:08:17 -0600595 + "SET icon=? WHERE _id=?");
Joe Onorato0589f0f2010-02-08 13:44:00 -0800596
597 c = db.rawQuery("SELECT _id, icon FROM favorites WHERE iconType=" +
598 Favorites.ICON_TYPE_BITMAP, null);
599
600 final int idIndex = c.getColumnIndexOrThrow(Favorites._ID);
601 final int iconIndex = c.getColumnIndexOrThrow(Favorites.ICON);
602
603 while (c.moveToNext()) {
604 long id = c.getLong(idIndex);
605 byte[] data = c.getBlob(iconIndex);
606 try {
607 Bitmap bitmap = Utilities.resampleIconBitmap(
608 BitmapFactory.decodeByteArray(data, 0, data.length),
609 mContext);
610 if (bitmap != null) {
611 update.bindLong(1, id);
612 data = ItemInfo.flattenBitmap(bitmap);
613 if (data != null) {
614 update.bindBlob(2, data);
615 update.execute();
616 }
617 bitmap.recycle();
Joe Onorato0589f0f2010-02-08 13:44:00 -0800618 }
619 } catch (Exception e) {
620 if (!logged) {
621 Log.e(TAG, "Failed normalizing icon " + id, e);
622 } else {
623 Log.e(TAG, "Also failed normalizing icon " + id);
624 }
625 logged = true;
626 }
627 }
Bjorn Bringert3a928e42010-02-19 11:15:40 +0000628 db.setTransactionSuccessful();
Joe Onorato0589f0f2010-02-08 13:44:00 -0800629 } catch (SQLException ex) {
630 Log.w(TAG, "Problem while allocating appWidgetIds for existing widgets", ex);
631 } finally {
632 db.endTransaction();
Joe Onorato9690b392010-03-23 17:34:37 -0400633 if (update != null) {
634 update.close();
635 }
Joe Onorato0589f0f2010-02-08 13:44:00 -0800636 if (c != null) {
637 c.close();
638 }
639 }
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700640 }
641
642 // Generates a new ID to use for an object in your database. This method should be only
643 // called from the main UI thread. As an exception, we do call it when we call the
644 // constructor from the worker thread; however, this doesn't extend until after the
645 // constructor is called, and we only pass a reference to LauncherProvider to LauncherApp
646 // after that point
647 public long generateNewId() {
648 if (mMaxId < 0) {
649 throw new RuntimeException("Error: max id was not initialized");
650 }
651 mMaxId += 1;
652 return mMaxId;
653 }
654
655 private long initializeMaxId(SQLiteDatabase db) {
656 Cursor c = db.rawQuery("SELECT MAX(_id) FROM favorites", null);
657
658 // get the result
659 final int maxIdIndex = 0;
660 long id = -1;
661 if (c != null && c.moveToNext()) {
662 id = c.getLong(maxIdIndex);
663 }
Michael Jurka5130e402011-10-13 04:55:35 -0700664 if (c != null) {
665 c.close();
666 }
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700667
668 if (id == -1) {
669 throw new RuntimeException("Error: could not query max id");
670 }
671
672 return id;
Joe Onorato0589f0f2010-02-08 13:44:00 -0800673 }
674
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800675 /**
The Android Open Source Project7376fae2009-03-11 12:11:58 -0700676 * Upgrade existing clock and photo frame widgets into their new widget
Bjorn Bringert93c45762009-12-16 13:19:47 +0000677 * equivalents.
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800678 */
679 private void convertWidgets(SQLiteDatabase db) {
Bjorn Bringert34251342009-12-15 13:33:11 +0000680 final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800681 final int[] bindSources = new int[] {
682 Favorites.ITEM_TYPE_WIDGET_CLOCK,
683 Favorites.ITEM_TYPE_WIDGET_PHOTO_FRAME,
Bjorn Bringert7984c942009-12-09 15:38:25 +0000684 Favorites.ITEM_TYPE_WIDGET_SEARCH,
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800685 };
Bjorn Bringert7984c942009-12-09 15:38:25 +0000686
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800687 final String selectWhere = buildOrWhereString(Favorites.ITEM_TYPE, bindSources);
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700688
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800689 Cursor c = null;
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700690
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800691 db.beginTransaction();
692 try {
693 // Select and iterate through each matching widget
Bjorn Bringert7984c942009-12-09 15:38:25 +0000694 c = db.query(TABLE_FAVORITES, new String[] { Favorites._ID, Favorites.ITEM_TYPE },
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800695 selectWhere, null, null, null, null);
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700696
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800697 if (LOGD) Log.d(TAG, "found upgrade cursor count=" + c.getCount());
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700698
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800699 final ContentValues values = new ContentValues();
700 while (c != null && c.moveToNext()) {
701 long favoriteId = c.getLong(0);
Bjorn Bringert7984c942009-12-09 15:38:25 +0000702 int favoriteType = c.getInt(1);
703
The Android Open Source Project7376fae2009-03-11 12:11:58 -0700704 // Allocate and update database with new appWidgetId
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800705 try {
The Android Open Source Project7376fae2009-03-11 12:11:58 -0700706 int appWidgetId = mAppWidgetHost.allocateAppWidgetId();
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700707
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800708 if (LOGD) {
709 Log.d(TAG, "allocated appWidgetId=" + appWidgetId
710 + " for favoriteId=" + favoriteId);
711 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800712 values.clear();
Bjorn Bringert7984c942009-12-09 15:38:25 +0000713 values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_APPWIDGET);
714 values.put(Favorites.APPWIDGET_ID, appWidgetId);
715
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800716 // Original widgets might not have valid spans when upgrading
Bjorn Bringert7984c942009-12-09 15:38:25 +0000717 if (favoriteType == Favorites.ITEM_TYPE_WIDGET_SEARCH) {
718 values.put(LauncherSettings.Favorites.SPANX, 4);
719 values.put(LauncherSettings.Favorites.SPANY, 1);
720 } else {
721 values.put(LauncherSettings.Favorites.SPANX, 2);
722 values.put(LauncherSettings.Favorites.SPANY, 2);
723 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800724
725 String updateWhere = Favorites._ID + "=" + favoriteId;
726 db.update(TABLE_FAVORITES, values, updateWhere, null);
Bjorn Bringert34251342009-12-15 13:33:11 +0000727
Bjorn Bringert34251342009-12-15 13:33:11 +0000728 if (favoriteType == Favorites.ITEM_TYPE_WIDGET_CLOCK) {
Michael Jurka8b805b12012-04-18 14:23:14 -0700729 // TODO: check return value
730 appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId,
Bjorn Bringert34251342009-12-15 13:33:11 +0000731 new ComponentName("com.android.alarmclock",
732 "com.android.alarmclock.AnalogAppWidgetProvider"));
733 } else if (favoriteType == Favorites.ITEM_TYPE_WIDGET_PHOTO_FRAME) {
Michael Jurka8b805b12012-04-18 14:23:14 -0700734 // TODO: check return value
735 appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId,
Bjorn Bringert34251342009-12-15 13:33:11 +0000736 new ComponentName("com.android.camera",
737 "com.android.camera.PhotoAppWidgetProvider"));
738 } else if (favoriteType == Favorites.ITEM_TYPE_WIDGET_SEARCH) {
Michael Jurka8b805b12012-04-18 14:23:14 -0700739 // TODO: check return value
740 appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId,
Bjorn Bringertcd8fec02010-01-14 13:26:43 +0000741 getSearchWidgetProvider());
Bjorn Bringert34251342009-12-15 13:33:11 +0000742 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800743 } catch (RuntimeException ex) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800744 Log.e(TAG, "Problem allocating appWidgetId", ex);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800745 }
746 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700747
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800748 db.setTransactionSuccessful();
749 } catch (SQLException ex) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800750 Log.w(TAG, "Problem while allocating appWidgetIds for existing widgets", ex);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800751 } finally {
752 db.endTransaction();
753 if (c != null) {
754 c.close();
755 }
756 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800757 }
758
Michael Jurka8b805b12012-04-18 14:23:14 -0700759 private static final void beginDocument(XmlPullParser parser, String firstElementName)
760 throws XmlPullParserException, IOException {
761 int type;
Michael Jurka9bc8eba2012-05-21 20:36:44 -0700762 while ((type = parser.next()) != XmlPullParser.START_TAG
763 && type != XmlPullParser.END_DOCUMENT) {
Michael Jurka8b805b12012-04-18 14:23:14 -0700764 ;
765 }
766
Michael Jurka9bc8eba2012-05-21 20:36:44 -0700767 if (type != XmlPullParser.START_TAG) {
Michael Jurka8b805b12012-04-18 14:23:14 -0700768 throw new XmlPullParserException("No start tag found");
769 }
770
771 if (!parser.getName().equals(firstElementName)) {
772 throw new XmlPullParserException("Unexpected start tag: found " + parser.getName() +
773 ", expected " + firstElementName);
774 }
775 }
776
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800777 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800778 * Loads the default set of favorite packages from an xml file.
779 *
780 * @param db The database to write the values into
Winson Chung3d503fb2011-07-13 17:25:49 -0700781 * @param filterContainerId The specific container id of items to load
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800782 */
Winson Chung6d092682011-11-16 18:43:26 -0800783 private int loadFavorites(SQLiteDatabase db, int workspaceResourceId) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800784 Intent intent = new Intent(Intent.ACTION_MAIN, null);
785 intent.addCategory(Intent.CATEGORY_LAUNCHER);
786 ContentValues values = new ContentValues();
787
788 PackageManager packageManager = mContext.getPackageManager();
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800789 int allAppsButtonRank =
790 mContext.getResources().getInteger(R.integer.hotseat_all_apps_index);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800791 int i = 0;
792 try {
Winson Chung6d092682011-11-16 18:43:26 -0800793 XmlResourceParser parser = mContext.getResources().getXml(workspaceResourceId);
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700794 AttributeSet attrs = Xml.asAttributeSet(parser);
Michael Jurka8b805b12012-04-18 14:23:14 -0700795 beginDocument(parser, TAG_FAVORITES);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800796
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700797 final int depth = parser.getDepth();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800798
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700799 int type;
800 while (((type = parser.next()) != XmlPullParser.END_TAG ||
801 parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
802
803 if (type != XmlPullParser.START_TAG) {
804 continue;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800805 }
806
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700807 boolean added = false;
808 final String name = parser.getName();
809
810 TypedArray a = mContext.obtainStyledAttributes(attrs, R.styleable.Favorite);
811
Winson Chung3d503fb2011-07-13 17:25:49 -0700812 long container = LauncherSettings.Favorites.CONTAINER_DESKTOP;
813 if (a.hasValue(R.styleable.Favorite_container)) {
814 container = Long.valueOf(a.getString(R.styleable.Favorite_container));
815 }
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700816
Winson Chung6d092682011-11-16 18:43:26 -0800817 String screen = a.getString(R.styleable.Favorite_screen);
818 String x = a.getString(R.styleable.Favorite_x);
819 String y = a.getString(R.styleable.Favorite_y);
820
821 // If we are adding to the hotseat, the screen is used as the position in the
822 // hotseat. This screen can't be at position 0 because AllApps is in the
823 // zeroth position.
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800824 if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT
825 && Integer.valueOf(screen) == allAppsButtonRank) {
Winson Chung6d092682011-11-16 18:43:26 -0800826 throw new RuntimeException("Invalid screen position for hotseat item");
827 }
828
829 values.clear();
830 values.put(LauncherSettings.Favorites.CONTAINER, container);
831 values.put(LauncherSettings.Favorites.SCREEN, screen);
832 values.put(LauncherSettings.Favorites.CELLX, x);
833 values.put(LauncherSettings.Favorites.CELLY, y);
834
835 if (TAG_FAVORITE.equals(name)) {
836 long id = addAppShortcut(db, values, a, packageManager, intent);
837 added = id >= 0;
838 } else if (TAG_SEARCH.equals(name)) {
839 added = addSearchWidget(db, values);
840 } else if (TAG_CLOCK.equals(name)) {
841 added = addClockWidget(db, values);
842 } else if (TAG_APPWIDGET.equals(name)) {
Winson Chungb3302ae2012-05-01 10:19:14 -0700843 added = addAppWidget(parser, attrs, type, db, values, a, packageManager);
Winson Chung6d092682011-11-16 18:43:26 -0800844 } else if (TAG_SHORTCUT.equals(name)) {
845 long id = addUriShortcut(db, values, a);
846 added = id >= 0;
847 } else if (TAG_FOLDER.equals(name)) {
848 String title;
849 int titleResId = a.getResourceId(R.styleable.Favorite_title, -1);
850 if (titleResId != -1) {
851 title = mContext.getResources().getString(titleResId);
852 } else {
853 title = mContext.getResources().getString(R.string.folder_name);
Winson Chung3d503fb2011-07-13 17:25:49 -0700854 }
Winson Chung6d092682011-11-16 18:43:26 -0800855 values.put(LauncherSettings.Favorites.TITLE, title);
856 long folderId = addFolder(db, values);
857 added = folderId >= 0;
Winson Chung3d503fb2011-07-13 17:25:49 -0700858
Winson Chung6d092682011-11-16 18:43:26 -0800859 ArrayList<Long> folderItems = new ArrayList<Long>();
Winson Chung3d503fb2011-07-13 17:25:49 -0700860
Winson Chung6d092682011-11-16 18:43:26 -0800861 int folderDepth = parser.getDepth();
862 while ((type = parser.next()) != XmlPullParser.END_TAG ||
863 parser.getDepth() > folderDepth) {
864 if (type != XmlPullParser.START_TAG) {
865 continue;
866 }
867 final String folder_item_name = parser.getName();
868
869 TypedArray ar = mContext.obtainStyledAttributes(attrs,
870 R.styleable.Favorite);
871 values.clear();
872 values.put(LauncherSettings.Favorites.CONTAINER, folderId);
873
874 if (TAG_FAVORITE.equals(folder_item_name) && folderId >= 0) {
875 long id =
876 addAppShortcut(db, values, ar, packageManager, intent);
877 if (id >= 0) {
878 folderItems.add(id);
879 }
880 } else if (TAG_SHORTCUT.equals(folder_item_name) && folderId >= 0) {
881 long id = addUriShortcut(db, values, ar);
882 if (id >= 0) {
883 folderItems.add(id);
884 }
Adam Cohen228da5a2011-07-27 22:23:47 -0700885 } else {
Winson Chung6d092682011-11-16 18:43:26 -0800886 throw new RuntimeException("Folders can " +
887 "contain only shortcuts");
Adam Cohen228da5a2011-07-27 22:23:47 -0700888 }
Winson Chung6d092682011-11-16 18:43:26 -0800889 ar.recycle();
890 }
891 // We can only have folders with >= 2 items, so we need to remove the
892 // folder and clean up if less than 2 items were included, or some
893 // failed to add, and less than 2 were actually added
894 if (folderItems.size() < 2 && folderId >= 0) {
895 // We just delete the folder and any items that made it
896 deleteId(db, folderId);
897 if (folderItems.size() > 0) {
898 deleteId(db, folderItems.get(0));
Adam Cohen228da5a2011-07-27 22:23:47 -0700899 }
Winson Chung6d092682011-11-16 18:43:26 -0800900 added = false;
Winson Chung3d503fb2011-07-13 17:25:49 -0700901 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800902 }
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700903 if (added) i++;
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700904 a.recycle();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800905 }
906 } catch (XmlPullParserException e) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800907 Log.w(TAG, "Got exception parsing favorites.", e);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800908 } catch (IOException e) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800909 Log.w(TAG, "Got exception parsing favorites.", e);
Winson Chung3d503fb2011-07-13 17:25:49 -0700910 } catch (RuntimeException e) {
911 Log.w(TAG, "Got exception parsing favorites.", e);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800912 }
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700913
914 return i;
915 }
916
Adam Cohen228da5a2011-07-27 22:23:47 -0700917 private long addAppShortcut(SQLiteDatabase db, ContentValues values, TypedArray a,
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700918 PackageManager packageManager, Intent intent) {
Adam Cohen228da5a2011-07-27 22:23:47 -0700919 long id = -1;
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700920 ActivityInfo info;
921 String packageName = a.getString(R.styleable.Favorite_packageName);
922 String className = a.getString(R.styleable.Favorite_className);
923 try {
Romain Guy693599f2010-03-23 10:58:18 -0700924 ComponentName cn;
925 try {
926 cn = new ComponentName(packageName, className);
927 info = packageManager.getActivityInfo(cn, 0);
928 } catch (PackageManager.NameNotFoundException nnfe) {
929 String[] packages = packageManager.currentToCanonicalPackageNames(
930 new String[] { packageName });
931 cn = new ComponentName(packages[0], className);
932 info = packageManager.getActivityInfo(cn, 0);
933 }
Adam Cohen228da5a2011-07-27 22:23:47 -0700934 id = generateNewId();
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700935 intent.setComponent(cn);
Romain Guy693599f2010-03-23 10:58:18 -0700936 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
937 Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
Romain Guy1ce1a242009-06-23 17:34:54 -0700938 values.put(Favorites.INTENT, intent.toUri(0));
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700939 values.put(Favorites.TITLE, info.loadLabel(packageManager).toString());
940 values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_APPLICATION);
941 values.put(Favorites.SPANX, 1);
942 values.put(Favorites.SPANY, 1);
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700943 values.put(Favorites._ID, generateNewId());
Adam Cohen228da5a2011-07-27 22:23:47 -0700944 if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values) < 0) {
945 return -1;
946 }
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700947 } catch (PackageManager.NameNotFoundException e) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800948 Log.w(TAG, "Unable to add favorite: " + packageName +
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700949 "/" + className, e);
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700950 }
Adam Cohen228da5a2011-07-27 22:23:47 -0700951 return id;
952 }
953
954 private long addFolder(SQLiteDatabase db, ContentValues values) {
955 values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_FOLDER);
956 values.put(Favorites.SPANX, 1);
957 values.put(Favorites.SPANY, 1);
958 long id = generateNewId();
959 values.put(Favorites._ID, id);
960 if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values) <= 0) {
961 return -1;
962 } else {
963 return id;
964 }
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700965 }
966
Bjorn Bringertcd8fec02010-01-14 13:26:43 +0000967 private ComponentName getSearchWidgetProvider() {
968 SearchManager searchManager =
969 (SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE);
970 ComponentName searchComponent = searchManager.getGlobalSearchActivity();
971 if (searchComponent == null) return null;
972 return getProviderInPackage(searchComponent.getPackageName());
973 }
974
975 /**
976 * Gets an appwidget provider from the given package. If the package contains more than
977 * one appwidget provider, an arbitrary one is returned.
978 */
979 private ComponentName getProviderInPackage(String packageName) {
980 AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext);
981 List<AppWidgetProviderInfo> providers = appWidgetManager.getInstalledProviders();
982 if (providers == null) return null;
983 final int providerCount = providers.size();
984 for (int i = 0; i < providerCount; i++) {
985 ComponentName provider = providers.get(i).provider;
986 if (provider != null && provider.getPackageName().equals(packageName)) {
987 return provider;
988 }
989 }
990 return null;
991 }
992
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700993 private boolean addSearchWidget(SQLiteDatabase db, ContentValues values) {
Bjorn Bringertcd8fec02010-01-14 13:26:43 +0000994 ComponentName cn = getSearchWidgetProvider();
Winson Chungb3302ae2012-05-01 10:19:14 -0700995 return addAppWidget(db, values, cn, 4, 1, null);
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700996 }
997
998 private boolean addClockWidget(SQLiteDatabase db, ContentValues values) {
Bjorn Bringert34251342009-12-15 13:33:11 +0000999 ComponentName cn = new ComponentName("com.android.alarmclock",
1000 "com.android.alarmclock.AnalogAppWidgetProvider");
Winson Chungb3302ae2012-05-01 10:19:14 -07001001 return addAppWidget(db, values, cn, 2, 2, null);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001002 }
Adam Cohen228da5a2011-07-27 22:23:47 -07001003
Winson Chungb3302ae2012-05-01 10:19:14 -07001004 private boolean addAppWidget(XmlResourceParser parser, AttributeSet attrs, int type,
1005 SQLiteDatabase db, ContentValues values, TypedArray a,
1006 PackageManager packageManager) throws XmlPullParserException, IOException {
Romain Guy693599f2010-03-23 10:58:18 -07001007
Mike Cleronb87bd162009-10-30 16:36:56 -07001008 String packageName = a.getString(R.styleable.Favorite_packageName);
1009 String className = a.getString(R.styleable.Favorite_className);
1010
1011 if (packageName == null || className == null) {
1012 return false;
1013 }
Romain Guy693599f2010-03-23 10:58:18 -07001014
1015 boolean hasPackage = true;
Mike Cleronb87bd162009-10-30 16:36:56 -07001016 ComponentName cn = new ComponentName(packageName, className);
Romain Guy693599f2010-03-23 10:58:18 -07001017 try {
1018 packageManager.getReceiverInfo(cn, 0);
1019 } catch (Exception e) {
1020 String[] packages = packageManager.currentToCanonicalPackageNames(
1021 new String[] { packageName });
1022 cn = new ComponentName(packages[0], className);
1023 try {
1024 packageManager.getReceiverInfo(cn, 0);
1025 } catch (Exception e1) {
1026 hasPackage = false;
1027 }
1028 }
1029
1030 if (hasPackage) {
1031 int spanX = a.getInt(R.styleable.Favorite_spanX, 0);
1032 int spanY = a.getInt(R.styleable.Favorite_spanY, 0);
Winson Chungb3302ae2012-05-01 10:19:14 -07001033
1034 // Read the extras
1035 Bundle extras = new Bundle();
1036 int widgetDepth = parser.getDepth();
1037 while ((type = parser.next()) != XmlPullParser.END_TAG ||
1038 parser.getDepth() > widgetDepth) {
1039 if (type != XmlPullParser.START_TAG) {
1040 continue;
1041 }
1042
1043 TypedArray ar = mContext.obtainStyledAttributes(attrs, R.styleable.Extra);
1044 if (TAG_EXTRA.equals(parser.getName())) {
1045 String key = ar.getString(R.styleable.Extra_key);
1046 String value = ar.getString(R.styleable.Extra_value);
1047 if (key != null && value != null) {
1048 extras.putString(key, value);
1049 } else {
1050 throw new RuntimeException("Widget extras must have a key and value");
1051 }
1052 } else {
1053 throw new RuntimeException("Widgets can contain only extras");
1054 }
1055 ar.recycle();
1056 }
1057
1058 return addAppWidget(db, values, cn, spanX, spanY, extras);
Romain Guy693599f2010-03-23 10:58:18 -07001059 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -07001060
Romain Guy693599f2010-03-23 10:58:18 -07001061 return false;
Bjorn Bringert7984c942009-12-09 15:38:25 +00001062 }
1063
1064 private boolean addAppWidget(SQLiteDatabase db, ContentValues values, ComponentName cn,
Winson Chungb3302ae2012-05-01 10:19:14 -07001065 int spanX, int spanY, Bundle extras) {
Mike Cleronb87bd162009-10-30 16:36:56 -07001066 boolean allocatedAppWidgets = false;
1067 final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext);
1068
1069 try {
1070 int appWidgetId = mAppWidgetHost.allocateAppWidgetId();
Daniel Lehmannc3a80402012-04-23 21:35:11 -07001071
Mike Cleronb87bd162009-10-30 16:36:56 -07001072 values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_APPWIDGET);
Bjorn Bringert7984c942009-12-09 15:38:25 +00001073 values.put(Favorites.SPANX, spanX);
1074 values.put(Favorites.SPANY, spanY);
Mike Cleronb87bd162009-10-30 16:36:56 -07001075 values.put(Favorites.APPWIDGET_ID, appWidgetId);
Michael Jurkaa8c760d2011-04-28 14:59:33 -07001076 values.put(Favorites._ID, generateNewId());
1077 dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values);
Mike Cleronb87bd162009-10-30 16:36:56 -07001078
1079 allocatedAppWidgets = true;
Daniel Lehmannc3a80402012-04-23 21:35:11 -07001080
Michael Jurka8b805b12012-04-18 14:23:14 -07001081 // TODO: need to check return value
1082 appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, cn);
Winson Chungb3302ae2012-05-01 10:19:14 -07001083
1084 // Send a broadcast to configure the widget
1085 if (extras != null && !extras.isEmpty()) {
1086 Intent intent = new Intent(ACTION_APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE);
1087 intent.setComponent(cn);
1088 intent.putExtras(extras);
1089 intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
1090 mContext.sendBroadcast(intent);
1091 }
Mike Cleronb87bd162009-10-30 16:36:56 -07001092 } catch (RuntimeException ex) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -08001093 Log.e(TAG, "Problem allocating appWidgetId", ex);
Mike Cleronb87bd162009-10-30 16:36:56 -07001094 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -07001095
Mike Cleronb87bd162009-10-30 16:36:56 -07001096 return allocatedAppWidgets;
1097 }
Adam Cohen228da5a2011-07-27 22:23:47 -07001098
1099 private long addUriShortcut(SQLiteDatabase db, ContentValues values,
Mike Cleronb87bd162009-10-30 16:36:56 -07001100 TypedArray a) {
1101 Resources r = mContext.getResources();
1102
1103 final int iconResId = a.getResourceId(R.styleable.Favorite_icon, 0);
1104 final int titleResId = a.getResourceId(R.styleable.Favorite_title, 0);
1105
Romain Guy7eb9e5e2009-12-02 20:10:07 -08001106 Intent intent;
Mike Cleronb87bd162009-10-30 16:36:56 -07001107 String uri = null;
1108 try {
1109 uri = a.getString(R.styleable.Favorite_uri);
1110 intent = Intent.parseUri(uri, 0);
1111 } catch (URISyntaxException e) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -08001112 Log.w(TAG, "Shortcut has malformed uri: " + uri);
Adam Cohen228da5a2011-07-27 22:23:47 -07001113 return -1; // Oh well
Mike Cleronb87bd162009-10-30 16:36:56 -07001114 }
1115
1116 if (iconResId == 0 || titleResId == 0) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -08001117 Log.w(TAG, "Shortcut is missing title or icon resource ID");
Adam Cohen228da5a2011-07-27 22:23:47 -07001118 return -1;
Mike Cleronb87bd162009-10-30 16:36:56 -07001119 }
1120
Adam Cohen228da5a2011-07-27 22:23:47 -07001121 long id = generateNewId();
Mike Cleronb87bd162009-10-30 16:36:56 -07001122 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1123 values.put(Favorites.INTENT, intent.toUri(0));
1124 values.put(Favorites.TITLE, r.getString(titleResId));
1125 values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_SHORTCUT);
1126 values.put(Favorites.SPANX, 1);
1127 values.put(Favorites.SPANY, 1);
1128 values.put(Favorites.ICON_TYPE, Favorites.ICON_TYPE_RESOURCE);
1129 values.put(Favorites.ICON_PACKAGE, mContext.getPackageName());
1130 values.put(Favorites.ICON_RESOURCE, r.getResourceName(iconResId));
Adam Cohen228da5a2011-07-27 22:23:47 -07001131 values.put(Favorites._ID, id);
Mike Cleronb87bd162009-10-30 16:36:56 -07001132
Adam Cohen228da5a2011-07-27 22:23:47 -07001133 if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values) < 0) {
1134 return -1;
1135 }
1136 return id;
Mike Cleronb87bd162009-10-30 16:36:56 -07001137 }
1138 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -07001139
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001140 /**
1141 * Build a query string that will match any row where the column matches
1142 * anything in the values list.
1143 */
1144 static String buildOrWhereString(String column, int[] values) {
1145 StringBuilder selectWhere = new StringBuilder();
1146 for (int i = values.length - 1; i >= 0; i--) {
1147 selectWhere.append(column).append("=").append(values[i]);
1148 if (i > 0) {
1149 selectWhere.append(" OR ");
1150 }
1151 }
1152 return selectWhere.toString();
1153 }
1154
1155 static class SqlArguments {
1156 public final String table;
1157 public final String where;
1158 public final String[] args;
1159
1160 SqlArguments(Uri url, String where, String[] args) {
1161 if (url.getPathSegments().size() == 1) {
1162 this.table = url.getPathSegments().get(0);
1163 this.where = where;
1164 this.args = args;
1165 } else if (url.getPathSegments().size() != 2) {
1166 throw new IllegalArgumentException("Invalid URI: " + url);
1167 } else if (!TextUtils.isEmpty(where)) {
1168 throw new UnsupportedOperationException("WHERE clause not supported: " + url);
1169 } else {
1170 this.table = url.getPathSegments().get(0);
Daniel Lehmannc3a80402012-04-23 21:35:11 -07001171 this.where = "_id=" + ContentUris.parseId(url);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001172 this.args = null;
1173 }
1174 }
1175
1176 SqlArguments(Uri url) {
1177 if (url.getPathSegments().size() == 1) {
1178 table = url.getPathSegments().get(0);
1179 where = null;
1180 args = null;
1181 } else {
1182 throw new IllegalArgumentException("Invalid URI: " + url);
1183 }
1184 }
1185 }
1186}