blob: 297c097e9976017733cf0cb10e91048f9c442f6b [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;
45import android.provider.Settings;
46import android.text.TextUtils;
47import android.util.AttributeSet;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080048import android.util.Log;
49import android.util.Xml;
Adam Cohen228da5a2011-07-27 22:23:47 -070050
Michael Jurka8b805b12012-04-18 14:23:14 -070051import com.android.launcher.R;
52import com.android.launcher2.LauncherSettings.Favorites;
53
54import org.xmlpull.v1.XmlPullParser;
55import org.xmlpull.v1.XmlPullParserException;
56
The Android Open Source Project31dd5032009-03-03 19:32:27 -080057import java.io.IOException;
Mike Cleronb87bd162009-10-30 16:36:56 -070058import java.net.URISyntaxException;
Adam Cohen228da5a2011-07-27 22:23:47 -070059import java.util.ArrayList;
Bjorn Bringertcd8fec02010-01-14 13:26:43 +000060import java.util.List;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080061
The Android Open Source Project31dd5032009-03-03 19:32:27 -080062public class LauncherProvider extends ContentProvider {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -080063 private static final String TAG = "Launcher.LauncherProvider";
64 private static final boolean LOGD = false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080065
66 private static final String DATABASE_NAME = "launcher.db";
Winson Chung3d503fb2011-07-13 17:25:49 -070067
Daniel Lehmannc3a80402012-04-23 21:35:11 -070068 private static final int DATABASE_VERSION = 10;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080069
Joe Onoratoa5902522009-07-30 13:37:37 -070070 static final String AUTHORITY = "com.android.launcher2.settings";
Winson Chung3d503fb2011-07-13 17:25:49 -070071
The Android Open Source Project31dd5032009-03-03 19:32:27 -080072 static final String TABLE_FAVORITES = "favorites";
73 static final String PARAMETER_NOTIFY = "notify";
Michael Jurkab85f8a42012-04-25 15:48:32 -070074 static final String DB_CREATED_BUT_DEFAULT_WORKSPACE_NOT_LOADED =
75 "DB_CREATED_BUT_DEFAULT_WORKSPACE_NOT_LOADED";
The Android Open Source Project31dd5032009-03-03 19:32:27 -080076
Jeffrey Sharkey2bbcae12009-03-31 14:37:57 -070077 /**
Romain Guy73b979d2009-06-09 12:57:21 -070078 * {@link Uri} triggered at any registered {@link android.database.ContentObserver} when
Jeffrey Sharkey2bbcae12009-03-31 14:37:57 -070079 * {@link AppWidgetHost#deleteHost()} is called during database creation.
80 * Use this to recall {@link AppWidgetHost#startListening()} if needed.
81 */
82 static final Uri CONTENT_APPWIDGET_RESET_URI =
83 Uri.parse("content://" + AUTHORITY + "/appWidgetReset");
Daniel Lehmannc3a80402012-04-23 21:35:11 -070084
Michael Jurkaa8c760d2011-04-28 14:59:33 -070085 private DatabaseHelper mOpenHelper;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080086
87 @Override
88 public boolean onCreate() {
89 mOpenHelper = new DatabaseHelper(getContext());
Michael Jurkaa8c760d2011-04-28 14:59:33 -070090 ((LauncherApplication) getContext()).setLauncherProvider(this);
The Android Open Source Project31dd5032009-03-03 19:32:27 -080091 return true;
92 }
93
94 @Override
95 public String getType(Uri uri) {
96 SqlArguments args = new SqlArguments(uri, null, null);
97 if (TextUtils.isEmpty(args.where)) {
98 return "vnd.android.cursor.dir/" + args.table;
99 } else {
100 return "vnd.android.cursor.item/" + args.table;
101 }
102 }
103
104 @Override
105 public Cursor query(Uri uri, String[] projection, String selection,
106 String[] selectionArgs, String sortOrder) {
107
108 SqlArguments args = new SqlArguments(uri, selection, selectionArgs);
109 SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
110 qb.setTables(args.table);
111
Romain Guy73b979d2009-06-09 12:57:21 -0700112 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800113 Cursor result = qb.query(db, projection, args.where, args.args, null, null, sortOrder);
114 result.setNotificationUri(getContext().getContentResolver(), uri);
115
116 return result;
117 }
118
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700119 private static long dbInsertAndCheck(DatabaseHelper helper,
120 SQLiteDatabase db, String table, String nullColumnHack, ContentValues values) {
121 if (!values.containsKey(LauncherSettings.Favorites._ID)) {
122 throw new RuntimeException("Error: attempting to add item without specifying an id");
123 }
124 return db.insert(table, nullColumnHack, values);
125 }
126
Adam Cohen228da5a2011-07-27 22:23:47 -0700127 private static void deleteId(SQLiteDatabase db, long id) {
128 Uri uri = LauncherSettings.Favorites.getContentUri(id, false);
129 SqlArguments args = new SqlArguments(uri, null, null);
130 db.delete(args.table, args.where, args.args);
131 }
132
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800133 @Override
134 public Uri insert(Uri uri, ContentValues initialValues) {
135 SqlArguments args = new SqlArguments(uri);
136
137 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700138 final long rowId = dbInsertAndCheck(mOpenHelper, db, args.table, null, initialValues);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800139 if (rowId <= 0) return null;
140
141 uri = ContentUris.withAppendedId(uri, rowId);
142 sendNotify(uri);
143
144 return uri;
145 }
146
147 @Override
148 public int bulkInsert(Uri uri, ContentValues[] values) {
149 SqlArguments args = new SqlArguments(uri);
150
151 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
152 db.beginTransaction();
153 try {
154 int numValues = values.length;
155 for (int i = 0; i < numValues; i++) {
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700156 if (dbInsertAndCheck(mOpenHelper, db, args.table, null, values[i]) < 0) {
157 return 0;
158 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800159 }
160 db.setTransactionSuccessful();
161 } finally {
162 db.endTransaction();
163 }
164
165 sendNotify(uri);
166 return values.length;
167 }
168
169 @Override
170 public int delete(Uri uri, String selection, String[] selectionArgs) {
171 SqlArguments args = new SqlArguments(uri, selection, selectionArgs);
172
173 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
174 int count = db.delete(args.table, args.where, args.args);
175 if (count > 0) sendNotify(uri);
176
177 return count;
178 }
179
180 @Override
181 public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
182 SqlArguments args = new SqlArguments(uri, selection, selectionArgs);
183
184 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
185 int count = db.update(args.table, values, args.where, args.args);
186 if (count > 0) sendNotify(uri);
187
188 return count;
189 }
190
191 private void sendNotify(Uri uri) {
192 String notify = uri.getQueryParameter(PARAMETER_NOTIFY);
193 if (notify == null || "true".equals(notify)) {
194 getContext().getContentResolver().notifyChange(uri, null);
195 }
196 }
197
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700198 public long generateNewId() {
199 return mOpenHelper.generateNewId();
200 }
201
Michael Jurkab85f8a42012-04-25 15:48:32 -0700202 public void loadDefaultFavoritesIfNecessary() {
203 String spKey = LauncherApplication.getSharedPreferencesKey();
204 SharedPreferences sp = getContext().getSharedPreferences(spKey, Context.MODE_PRIVATE);
205 if (sp.getBoolean(DB_CREATED_BUT_DEFAULT_WORKSPACE_NOT_LOADED, false)) {
206 // Populate favorites table with initial favorites
207 SharedPreferences.Editor editor = sp.edit();
208 editor.remove(DB_CREATED_BUT_DEFAULT_WORKSPACE_NOT_LOADED);
209 mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(), R.xml.default_workspace);
210 editor.commit();
211 }
212 }
213
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800214 private static class DatabaseHelper extends SQLiteOpenHelper {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800215 private static final String TAG_FAVORITES = "favorites";
216 private static final String TAG_FAVORITE = "favorite";
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700217 private static final String TAG_CLOCK = "clock";
218 private static final String TAG_SEARCH = "search";
Mike Cleronb87bd162009-10-30 16:36:56 -0700219 private static final String TAG_APPWIDGET = "appwidget";
220 private static final String TAG_SHORTCUT = "shortcut";
Adam Cohen228da5a2011-07-27 22:23:47 -0700221 private static final String TAG_FOLDER = "folder";
Winson Chung3d503fb2011-07-13 17:25:49 -0700222
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800223 private final Context mContext;
The Android Open Source Project7376fae2009-03-11 12:11:58 -0700224 private final AppWidgetHost mAppWidgetHost;
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700225 private long mMaxId = -1;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800226
227 DatabaseHelper(Context context) {
228 super(context, DATABASE_NAME, null, DATABASE_VERSION);
229 mContext = context;
The Android Open Source Project7376fae2009-03-11 12:11:58 -0700230 mAppWidgetHost = new AppWidgetHost(context, Launcher.APPWIDGET_HOST_ID);
Winson Chung3d503fb2011-07-13 17:25:49 -0700231
232 // In the case where neither onCreate nor onUpgrade gets called, we read the maxId from
233 // the DB here
234 if (mMaxId == -1) {
235 mMaxId = initializeMaxId(getWritableDatabase());
236 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800237 }
238
Jeffrey Sharkey2bbcae12009-03-31 14:37:57 -0700239 /**
240 * Send notification that we've deleted the {@link AppWidgetHost},
241 * probably as part of the initial database creation. The receiver may
242 * want to re-call {@link AppWidgetHost#startListening()} to ensure
243 * callbacks are correctly set.
244 */
245 private void sendAppWidgetResetNotify() {
246 final ContentResolver resolver = mContext.getContentResolver();
247 resolver.notifyChange(CONTENT_APPWIDGET_RESET_URI, null);
248 }
249
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800250 @Override
251 public void onCreate(SQLiteDatabase db) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800252 if (LOGD) Log.d(TAG, "creating new launcher database");
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700253
254 mMaxId = 1;
255
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800256 db.execSQL("CREATE TABLE favorites (" +
257 "_id INTEGER PRIMARY KEY," +
258 "title TEXT," +
259 "intent TEXT," +
260 "container INTEGER," +
261 "screen INTEGER," +
262 "cellX INTEGER," +
263 "cellY INTEGER," +
264 "spanX INTEGER," +
265 "spanY INTEGER," +
266 "itemType INTEGER," +
The Android Open Source Projectca9475f2009-03-13 13:04:24 -0700267 "appWidgetId INTEGER NOT NULL DEFAULT -1," +
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800268 "isShortcut INTEGER," +
269 "iconType INTEGER," +
270 "iconPackage TEXT," +
271 "iconResource TEXT," +
272 "icon BLOB," +
273 "uri TEXT," +
274 "displayMode INTEGER" +
275 ");");
276
The Android Open Source Project7376fae2009-03-11 12:11:58 -0700277 // Database was just created, so wipe any previous widgets
278 if (mAppWidgetHost != null) {
279 mAppWidgetHost.deleteHost();
Jeffrey Sharkey2bbcae12009-03-31 14:37:57 -0700280 sendAppWidgetResetNotify();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800281 }
Winson Chung3d503fb2011-07-13 17:25:49 -0700282
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800283 if (!convertDatabase(db)) {
Michael Jurkab85f8a42012-04-25 15:48:32 -0700284 // Set a shared pref so that we know we need to load the default workspace later
285 setFlagToLoadDefaultWorkspaceLater();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800286 }
287 }
288
Michael Jurkab85f8a42012-04-25 15:48:32 -0700289 private void setFlagToLoadDefaultWorkspaceLater() {
290 String spKey = LauncherApplication.getSharedPreferencesKey();
291 SharedPreferences sp = mContext.getSharedPreferences(spKey, Context.MODE_PRIVATE);
292 SharedPreferences.Editor editor = sp.edit();
293 editor.putBoolean(DB_CREATED_BUT_DEFAULT_WORKSPACE_NOT_LOADED, true);
294 editor.commit();
295 }
296
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800297 private boolean convertDatabase(SQLiteDatabase db) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800298 if (LOGD) Log.d(TAG, "converting database from an older format, but not onUpgrade");
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800299 boolean converted = false;
300
301 final Uri uri = Uri.parse("content://" + Settings.AUTHORITY +
302 "/old_favorites?notify=true");
303 final ContentResolver resolver = mContext.getContentResolver();
304 Cursor cursor = null;
305
306 try {
307 cursor = resolver.query(uri, null, null, null, null);
308 } catch (Exception e) {
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700309 // Ignore
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800310 }
311
312 // We already have a favorites database in the old provider
313 if (cursor != null && cursor.getCount() > 0) {
314 try {
315 converted = copyFromCursor(db, cursor) > 0;
316 } finally {
317 cursor.close();
318 }
319
320 if (converted) {
321 resolver.delete(uri, null, null);
322 }
323 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700324
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800325 if (converted) {
The Android Open Source Project7376fae2009-03-11 12:11:58 -0700326 // Convert widgets from this import into widgets
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800327 if (LOGD) Log.d(TAG, "converted and now triggering widget upgrade");
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800328 convertWidgets(db);
329 }
330
331 return converted;
332 }
333
334 private int copyFromCursor(SQLiteDatabase db, Cursor c) {
Romain Guy73b979d2009-06-09 12:57:21 -0700335 final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800336 final int intentIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.INTENT);
337 final int titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE);
338 final int iconTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_TYPE);
339 final int iconIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON);
340 final int iconPackageIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_PACKAGE);
341 final int iconResourceIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_RESOURCE);
342 final int containerIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER);
343 final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE);
344 final int screenIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN);
345 final int cellXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX);
346 final int cellYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY);
347 final int uriIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.URI);
348 final int displayModeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.DISPLAY_MODE);
349
350 ContentValues[] rows = new ContentValues[c.getCount()];
351 int i = 0;
352 while (c.moveToNext()) {
353 ContentValues values = new ContentValues(c.getColumnCount());
Romain Guy73b979d2009-06-09 12:57:21 -0700354 values.put(LauncherSettings.Favorites._ID, c.getLong(idIndex));
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800355 values.put(LauncherSettings.Favorites.INTENT, c.getString(intentIndex));
356 values.put(LauncherSettings.Favorites.TITLE, c.getString(titleIndex));
357 values.put(LauncherSettings.Favorites.ICON_TYPE, c.getInt(iconTypeIndex));
358 values.put(LauncherSettings.Favorites.ICON, c.getBlob(iconIndex));
359 values.put(LauncherSettings.Favorites.ICON_PACKAGE, c.getString(iconPackageIndex));
360 values.put(LauncherSettings.Favorites.ICON_RESOURCE, c.getString(iconResourceIndex));
361 values.put(LauncherSettings.Favorites.CONTAINER, c.getInt(containerIndex));
362 values.put(LauncherSettings.Favorites.ITEM_TYPE, c.getInt(itemTypeIndex));
The Android Open Source Project7376fae2009-03-11 12:11:58 -0700363 values.put(LauncherSettings.Favorites.APPWIDGET_ID, -1);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800364 values.put(LauncherSettings.Favorites.SCREEN, c.getInt(screenIndex));
365 values.put(LauncherSettings.Favorites.CELLX, c.getInt(cellXIndex));
366 values.put(LauncherSettings.Favorites.CELLY, c.getInt(cellYIndex));
367 values.put(LauncherSettings.Favorites.URI, c.getString(uriIndex));
368 values.put(LauncherSettings.Favorites.DISPLAY_MODE, c.getInt(displayModeIndex));
369 rows[i++] = values;
370 }
371
372 db.beginTransaction();
373 int total = 0;
374 try {
375 int numValues = rows.length;
376 for (i = 0; i < numValues; i++) {
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700377 if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, rows[i]) < 0) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800378 return 0;
379 } else {
380 total++;
381 }
382 }
383 db.setTransactionSuccessful();
384 } finally {
385 db.endTransaction();
386 }
387
388 return total;
389 }
390
391 @Override
392 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800393 if (LOGD) Log.d(TAG, "onUpgrade triggered");
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700394
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800395 int version = oldVersion;
The Android Open Source Projectca9475f2009-03-13 13:04:24 -0700396 if (version < 3) {
397 // upgrade 1,2 -> 3 added appWidgetId column
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800398 db.beginTransaction();
399 try {
The Android Open Source Project7376fae2009-03-11 12:11:58 -0700400 // Insert new column for holding appWidgetIds
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800401 db.execSQL("ALTER TABLE favorites " +
The Android Open Source Projectca9475f2009-03-13 13:04:24 -0700402 "ADD COLUMN appWidgetId INTEGER NOT NULL DEFAULT -1;");
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800403 db.setTransactionSuccessful();
The Android Open Source Projectca9475f2009-03-13 13:04:24 -0700404 version = 3;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800405 } catch (SQLException ex) {
406 // Old version remains, which means we wipe old data
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800407 Log.e(TAG, ex.getMessage(), ex);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800408 } finally {
409 db.endTransaction();
410 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700411
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800412 // Convert existing widgets only if table upgrade was successful
The Android Open Source Projectca9475f2009-03-13 13:04:24 -0700413 if (version == 3) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800414 convertWidgets(db);
415 }
416 }
Romain Guy73b979d2009-06-09 12:57:21 -0700417
418 if (version < 4) {
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800419 version = 4;
Romain Guy73b979d2009-06-09 12:57:21 -0700420 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700421
Romain Guy509cd6a2010-03-23 15:10:56 -0700422 // Where's version 5?
423 // - Donut and sholes on 2.0 shipped with version 4 of launcher1.
424 // - Passion shipped on 2.1 with version 6 of launcher2
425 // - Sholes shipped on 2.1r1 (aka Mr. 3) with version 5 of launcher 1
426 // but version 5 on there was the updateContactsShortcuts change
427 // which was version 6 in launcher 2 (first shipped on passion 2.1r1).
428 // The updateContactsShortcuts change is idempotent, so running it twice
429 // is okay so we'll do that when upgrading the devices that shipped with it.
430 if (version < 6) {
Mike Cleron3a2b3f22009-11-05 17:17:42 -0800431 // We went from 3 to 5 screens. Move everything 1 to the right
432 db.beginTransaction();
433 try {
434 db.execSQL("UPDATE favorites SET screen=(screen + 1);");
435 db.setTransactionSuccessful();
Mike Cleron3a2b3f22009-11-05 17:17:42 -0800436 } catch (SQLException ex) {
437 // Old version remains, which means we wipe old data
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800438 Log.e(TAG, ex.getMessage(), ex);
Mike Cleron3a2b3f22009-11-05 17:17:42 -0800439 } finally {
440 db.endTransaction();
441 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700442
Romain Guy509cd6a2010-03-23 15:10:56 -0700443 // We added the fast track.
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800444 if (updateContactsShortcuts(db)) {
445 version = 6;
446 }
447 }
Bjorn Bringert7984c942009-12-09 15:38:25 +0000448
449 if (version < 7) {
450 // Version 7 gets rid of the special search widget.
451 convertWidgets(db);
452 version = 7;
453 }
454
Joe Onorato0589f0f2010-02-08 13:44:00 -0800455 if (version < 8) {
456 // Version 8 (froyo) has the icons all normalized. This should
457 // already be the case in practice, but we now rely on it and don't
458 // resample the images each time.
459 normalizeIcons(db);
460 version = 8;
461 }
462
Winson Chung3d503fb2011-07-13 17:25:49 -0700463 if (version < 9) {
464 // The max id is not yet set at this point (onUpgrade is triggered in the ctor
465 // before it gets a change to get set, so we need to read it here when we use it)
466 if (mMaxId == -1) {
467 mMaxId = initializeMaxId(db);
468 }
469
470 // Add default hotseat icons
Winson Chung6d092682011-11-16 18:43:26 -0800471 loadFavorites(db, R.xml.update_workspace);
Winson Chung3d503fb2011-07-13 17:25:49 -0700472 version = 9;
473 }
474
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700475 if (version < 10) {
476 // Contact shortcuts need a different set of flags to be launched now
477 // The updateContactsShortcuts change is idempotent, so we can keep using it like
478 // back in the Donut days
479 updateContactsShortcuts(db);
480 version = 10;
481 }
482
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800483 if (version != DATABASE_VERSION) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800484 Log.w(TAG, "Destroying all old data.");
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800485 db.execSQL("DROP TABLE IF EXISTS " + TABLE_FAVORITES);
486 onCreate(db);
487 }
488 }
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800489
490 private boolean updateContactsShortcuts(SQLiteDatabase db) {
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800491 final String selectWhere = buildOrWhereString(Favorites.ITEM_TYPE,
492 new int[] { Favorites.ITEM_TYPE_SHORTCUT });
493
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700494 Cursor c = null;
495 final String actionQuickContact = "com.android.contacts.action.QUICK_CONTACT";
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800496 db.beginTransaction();
497 try {
498 // Select and iterate through each matching widget
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700499 c = db.query(TABLE_FAVORITES,
500 new String[] { Favorites._ID, Favorites.INTENT },
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800501 selectWhere, null, null, null, null);
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700502 if (c == null) return false;
503
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800504 if (LOGD) Log.d(TAG, "found upgrade cursor count=" + c.getCount());
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700505
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800506 final int idIndex = c.getColumnIndex(Favorites._ID);
507 final int intentIndex = c.getColumnIndex(Favorites.INTENT);
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700508
509 while (c.moveToNext()) {
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800510 long favoriteId = c.getLong(idIndex);
511 final String intentUri = c.getString(intentIndex);
512 if (intentUri != null) {
513 try {
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700514 final Intent intent = Intent.parseUri(intentUri, 0);
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800515 android.util.Log.d("Home", intent.toString());
516 final Uri uri = intent.getData();
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700517 if (uri != null) {
518 final String data = uri.toString();
519 if ((Intent.ACTION_VIEW.equals(intent.getAction()) ||
520 actionQuickContact.equals(intent.getAction())) &&
521 (data.startsWith("content://contacts/people/") ||
522 data.startsWith("content://com.android.contacts/" +
523 "contacts/lookup/"))) {
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800524
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700525 final Intent newIntent = new Intent(actionQuickContact);
526 // When starting from the launcher, start in a new, cleared task
527 // CLEAR_WHEN_TASK_RESET cannot reset the root of a task, so we
528 // clear the whole thing preemptively here since
529 // QuickContactActivity will finish itself when launching other
530 // detail activities.
531 newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
532 Intent.FLAG_ACTIVITY_CLEAR_TASK);
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800533
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700534 newIntent.setData(uri);
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800535
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700536 final ContentValues values = new ContentValues();
537 values.put(LauncherSettings.Favorites.INTENT,
538 newIntent.toUri(0));
539
540 String updateWhere = Favorites._ID + "=" + favoriteId;
541 db.update(TABLE_FAVORITES, values, updateWhere, null);
542 }
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800543 }
544 } catch (RuntimeException ex) {
545 Log.e(TAG, "Problem upgrading shortcut", ex);
546 } catch (URISyntaxException e) {
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700547 Log.e(TAG, "Problem upgrading shortcut", e);
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800548 }
549 }
550 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700551
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800552 db.setTransactionSuccessful();
553 } catch (SQLException ex) {
554 Log.w(TAG, "Problem while upgrading contacts", ex);
555 return false;
556 } finally {
557 db.endTransaction();
558 if (c != null) {
559 c.close();
560 }
561 }
562
563 return true;
564 }
565
Joe Onorato0589f0f2010-02-08 13:44:00 -0800566 private void normalizeIcons(SQLiteDatabase db) {
567 Log.d(TAG, "normalizing icons");
568
Joe Onorato346e1292010-02-18 10:34:24 -0500569 db.beginTransaction();
Joe Onorato0589f0f2010-02-08 13:44:00 -0800570 Cursor c = null;
Joe Onorato9690b392010-03-23 17:34:37 -0400571 SQLiteStatement update = null;
Joe Onorato0589f0f2010-02-08 13:44:00 -0800572 try {
573 boolean logged = false;
Joe Onorato9690b392010-03-23 17:34:37 -0400574 update = db.compileStatement("UPDATE favorites "
Jeff Hamiltoneaf77d62010-02-13 00:08:17 -0600575 + "SET icon=? WHERE _id=?");
Joe Onorato0589f0f2010-02-08 13:44:00 -0800576
577 c = db.rawQuery("SELECT _id, icon FROM favorites WHERE iconType=" +
578 Favorites.ICON_TYPE_BITMAP, null);
579
580 final int idIndex = c.getColumnIndexOrThrow(Favorites._ID);
581 final int iconIndex = c.getColumnIndexOrThrow(Favorites.ICON);
582
583 while (c.moveToNext()) {
584 long id = c.getLong(idIndex);
585 byte[] data = c.getBlob(iconIndex);
586 try {
587 Bitmap bitmap = Utilities.resampleIconBitmap(
588 BitmapFactory.decodeByteArray(data, 0, data.length),
589 mContext);
590 if (bitmap != null) {
591 update.bindLong(1, id);
592 data = ItemInfo.flattenBitmap(bitmap);
593 if (data != null) {
594 update.bindBlob(2, data);
595 update.execute();
596 }
597 bitmap.recycle();
Joe Onorato0589f0f2010-02-08 13:44:00 -0800598 }
599 } catch (Exception e) {
600 if (!logged) {
601 Log.e(TAG, "Failed normalizing icon " + id, e);
602 } else {
603 Log.e(TAG, "Also failed normalizing icon " + id);
604 }
605 logged = true;
606 }
607 }
Bjorn Bringert3a928e42010-02-19 11:15:40 +0000608 db.setTransactionSuccessful();
Joe Onorato0589f0f2010-02-08 13:44:00 -0800609 } catch (SQLException ex) {
610 Log.w(TAG, "Problem while allocating appWidgetIds for existing widgets", ex);
611 } finally {
612 db.endTransaction();
Joe Onorato9690b392010-03-23 17:34:37 -0400613 if (update != null) {
614 update.close();
615 }
Joe Onorato0589f0f2010-02-08 13:44:00 -0800616 if (c != null) {
617 c.close();
618 }
619 }
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700620 }
621
622 // Generates a new ID to use for an object in your database. This method should be only
623 // called from the main UI thread. As an exception, we do call it when we call the
624 // constructor from the worker thread; however, this doesn't extend until after the
625 // constructor is called, and we only pass a reference to LauncherProvider to LauncherApp
626 // after that point
627 public long generateNewId() {
628 if (mMaxId < 0) {
629 throw new RuntimeException("Error: max id was not initialized");
630 }
631 mMaxId += 1;
632 return mMaxId;
633 }
634
635 private long initializeMaxId(SQLiteDatabase db) {
636 Cursor c = db.rawQuery("SELECT MAX(_id) FROM favorites", null);
637
638 // get the result
639 final int maxIdIndex = 0;
640 long id = -1;
641 if (c != null && c.moveToNext()) {
642 id = c.getLong(maxIdIndex);
643 }
Michael Jurka5130e402011-10-13 04:55:35 -0700644 if (c != null) {
645 c.close();
646 }
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700647
648 if (id == -1) {
649 throw new RuntimeException("Error: could not query max id");
650 }
651
652 return id;
Joe Onorato0589f0f2010-02-08 13:44:00 -0800653 }
654
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800655 /**
The Android Open Source Project7376fae2009-03-11 12:11:58 -0700656 * Upgrade existing clock and photo frame widgets into their new widget
Bjorn Bringert93c45762009-12-16 13:19:47 +0000657 * equivalents.
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800658 */
659 private void convertWidgets(SQLiteDatabase db) {
Bjorn Bringert34251342009-12-15 13:33:11 +0000660 final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800661 final int[] bindSources = new int[] {
662 Favorites.ITEM_TYPE_WIDGET_CLOCK,
663 Favorites.ITEM_TYPE_WIDGET_PHOTO_FRAME,
Bjorn Bringert7984c942009-12-09 15:38:25 +0000664 Favorites.ITEM_TYPE_WIDGET_SEARCH,
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800665 };
Bjorn Bringert7984c942009-12-09 15:38:25 +0000666
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800667 final String selectWhere = buildOrWhereString(Favorites.ITEM_TYPE, bindSources);
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700668
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800669 Cursor c = null;
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700670
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800671 db.beginTransaction();
672 try {
673 // Select and iterate through each matching widget
Bjorn Bringert7984c942009-12-09 15:38:25 +0000674 c = db.query(TABLE_FAVORITES, new String[] { Favorites._ID, Favorites.ITEM_TYPE },
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800675 selectWhere, null, null, null, null);
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700676
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800677 if (LOGD) Log.d(TAG, "found upgrade cursor count=" + c.getCount());
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700678
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800679 final ContentValues values = new ContentValues();
680 while (c != null && c.moveToNext()) {
681 long favoriteId = c.getLong(0);
Bjorn Bringert7984c942009-12-09 15:38:25 +0000682 int favoriteType = c.getInt(1);
683
The Android Open Source Project7376fae2009-03-11 12:11:58 -0700684 // Allocate and update database with new appWidgetId
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800685 try {
The Android Open Source Project7376fae2009-03-11 12:11:58 -0700686 int appWidgetId = mAppWidgetHost.allocateAppWidgetId();
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700687
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800688 if (LOGD) {
689 Log.d(TAG, "allocated appWidgetId=" + appWidgetId
690 + " for favoriteId=" + favoriteId);
691 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800692 values.clear();
Bjorn Bringert7984c942009-12-09 15:38:25 +0000693 values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_APPWIDGET);
694 values.put(Favorites.APPWIDGET_ID, appWidgetId);
695
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800696 // Original widgets might not have valid spans when upgrading
Bjorn Bringert7984c942009-12-09 15:38:25 +0000697 if (favoriteType == Favorites.ITEM_TYPE_WIDGET_SEARCH) {
698 values.put(LauncherSettings.Favorites.SPANX, 4);
699 values.put(LauncherSettings.Favorites.SPANY, 1);
700 } else {
701 values.put(LauncherSettings.Favorites.SPANX, 2);
702 values.put(LauncherSettings.Favorites.SPANY, 2);
703 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800704
705 String updateWhere = Favorites._ID + "=" + favoriteId;
706 db.update(TABLE_FAVORITES, values, updateWhere, null);
Bjorn Bringert34251342009-12-15 13:33:11 +0000707
Bjorn Bringert34251342009-12-15 13:33:11 +0000708 if (favoriteType == Favorites.ITEM_TYPE_WIDGET_CLOCK) {
Michael Jurka8b805b12012-04-18 14:23:14 -0700709 // TODO: check return value
710 appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId,
Bjorn Bringert34251342009-12-15 13:33:11 +0000711 new ComponentName("com.android.alarmclock",
712 "com.android.alarmclock.AnalogAppWidgetProvider"));
713 } else if (favoriteType == Favorites.ITEM_TYPE_WIDGET_PHOTO_FRAME) {
Michael Jurka8b805b12012-04-18 14:23:14 -0700714 // TODO: check return value
715 appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId,
Bjorn Bringert34251342009-12-15 13:33:11 +0000716 new ComponentName("com.android.camera",
717 "com.android.camera.PhotoAppWidgetProvider"));
718 } else if (favoriteType == Favorites.ITEM_TYPE_WIDGET_SEARCH) {
Michael Jurka8b805b12012-04-18 14:23:14 -0700719 // TODO: check return value
720 appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId,
Bjorn Bringertcd8fec02010-01-14 13:26:43 +0000721 getSearchWidgetProvider());
Bjorn Bringert34251342009-12-15 13:33:11 +0000722 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800723 } catch (RuntimeException ex) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800724 Log.e(TAG, "Problem allocating appWidgetId", ex);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800725 }
726 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700727
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800728 db.setTransactionSuccessful();
729 } catch (SQLException ex) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800730 Log.w(TAG, "Problem while allocating appWidgetIds for existing widgets", ex);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800731 } finally {
732 db.endTransaction();
733 if (c != null) {
734 c.close();
735 }
736 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800737 }
738
Michael Jurka8b805b12012-04-18 14:23:14 -0700739 private static final void beginDocument(XmlPullParser parser, String firstElementName)
740 throws XmlPullParserException, IOException {
741 int type;
742 while ((type = parser.next()) != parser.START_TAG
743 && type != parser.END_DOCUMENT) {
744 ;
745 }
746
747 if (type != parser.START_TAG) {
748 throw new XmlPullParserException("No start tag found");
749 }
750
751 if (!parser.getName().equals(firstElementName)) {
752 throw new XmlPullParserException("Unexpected start tag: found " + parser.getName() +
753 ", expected " + firstElementName);
754 }
755 }
756
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800757 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800758 * Loads the default set of favorite packages from an xml file.
759 *
760 * @param db The database to write the values into
Winson Chung3d503fb2011-07-13 17:25:49 -0700761 * @param filterContainerId The specific container id of items to load
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800762 */
Winson Chung6d092682011-11-16 18:43:26 -0800763 private int loadFavorites(SQLiteDatabase db, int workspaceResourceId) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800764 Intent intent = new Intent(Intent.ACTION_MAIN, null);
765 intent.addCategory(Intent.CATEGORY_LAUNCHER);
766 ContentValues values = new ContentValues();
767
768 PackageManager packageManager = mContext.getPackageManager();
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800769 int allAppsButtonRank =
770 mContext.getResources().getInteger(R.integer.hotseat_all_apps_index);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800771 int i = 0;
772 try {
Winson Chung6d092682011-11-16 18:43:26 -0800773 XmlResourceParser parser = mContext.getResources().getXml(workspaceResourceId);
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700774 AttributeSet attrs = Xml.asAttributeSet(parser);
Michael Jurka8b805b12012-04-18 14:23:14 -0700775 beginDocument(parser, TAG_FAVORITES);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800776
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700777 final int depth = parser.getDepth();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800778
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700779 int type;
780 while (((type = parser.next()) != XmlPullParser.END_TAG ||
781 parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
782
783 if (type != XmlPullParser.START_TAG) {
784 continue;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800785 }
786
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700787 boolean added = false;
788 final String name = parser.getName();
789
790 TypedArray a = mContext.obtainStyledAttributes(attrs, R.styleable.Favorite);
791
Winson Chung3d503fb2011-07-13 17:25:49 -0700792 long container = LauncherSettings.Favorites.CONTAINER_DESKTOP;
793 if (a.hasValue(R.styleable.Favorite_container)) {
794 container = Long.valueOf(a.getString(R.styleable.Favorite_container));
795 }
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700796
Winson Chung6d092682011-11-16 18:43:26 -0800797 String screen = a.getString(R.styleable.Favorite_screen);
798 String x = a.getString(R.styleable.Favorite_x);
799 String y = a.getString(R.styleable.Favorite_y);
800
801 // If we are adding to the hotseat, the screen is used as the position in the
802 // hotseat. This screen can't be at position 0 because AllApps is in the
803 // zeroth position.
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800804 if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT
805 && Integer.valueOf(screen) == allAppsButtonRank) {
Winson Chung6d092682011-11-16 18:43:26 -0800806 throw new RuntimeException("Invalid screen position for hotseat item");
807 }
808
809 values.clear();
810 values.put(LauncherSettings.Favorites.CONTAINER, container);
811 values.put(LauncherSettings.Favorites.SCREEN, screen);
812 values.put(LauncherSettings.Favorites.CELLX, x);
813 values.put(LauncherSettings.Favorites.CELLY, y);
814
815 if (TAG_FAVORITE.equals(name)) {
816 long id = addAppShortcut(db, values, a, packageManager, intent);
817 added = id >= 0;
818 } else if (TAG_SEARCH.equals(name)) {
819 added = addSearchWidget(db, values);
820 } else if (TAG_CLOCK.equals(name)) {
821 added = addClockWidget(db, values);
822 } else if (TAG_APPWIDGET.equals(name)) {
823 added = addAppWidget(db, values, a, packageManager);
824 } else if (TAG_SHORTCUT.equals(name)) {
825 long id = addUriShortcut(db, values, a);
826 added = id >= 0;
827 } else if (TAG_FOLDER.equals(name)) {
828 String title;
829 int titleResId = a.getResourceId(R.styleable.Favorite_title, -1);
830 if (titleResId != -1) {
831 title = mContext.getResources().getString(titleResId);
832 } else {
833 title = mContext.getResources().getString(R.string.folder_name);
Winson Chung3d503fb2011-07-13 17:25:49 -0700834 }
Winson Chung6d092682011-11-16 18:43:26 -0800835 values.put(LauncherSettings.Favorites.TITLE, title);
836 long folderId = addFolder(db, values);
837 added = folderId >= 0;
Winson Chung3d503fb2011-07-13 17:25:49 -0700838
Winson Chung6d092682011-11-16 18:43:26 -0800839 ArrayList<Long> folderItems = new ArrayList<Long>();
Winson Chung3d503fb2011-07-13 17:25:49 -0700840
Winson Chung6d092682011-11-16 18:43:26 -0800841 int folderDepth = parser.getDepth();
842 while ((type = parser.next()) != XmlPullParser.END_TAG ||
843 parser.getDepth() > folderDepth) {
844 if (type != XmlPullParser.START_TAG) {
845 continue;
846 }
847 final String folder_item_name = parser.getName();
848
849 TypedArray ar = mContext.obtainStyledAttributes(attrs,
850 R.styleable.Favorite);
851 values.clear();
852 values.put(LauncherSettings.Favorites.CONTAINER, folderId);
853
854 if (TAG_FAVORITE.equals(folder_item_name) && folderId >= 0) {
855 long id =
856 addAppShortcut(db, values, ar, packageManager, intent);
857 if (id >= 0) {
858 folderItems.add(id);
859 }
860 } else if (TAG_SHORTCUT.equals(folder_item_name) && folderId >= 0) {
861 long id = addUriShortcut(db, values, ar);
862 if (id >= 0) {
863 folderItems.add(id);
864 }
Adam Cohen228da5a2011-07-27 22:23:47 -0700865 } else {
Winson Chung6d092682011-11-16 18:43:26 -0800866 throw new RuntimeException("Folders can " +
867 "contain only shortcuts");
Adam Cohen228da5a2011-07-27 22:23:47 -0700868 }
Winson Chung6d092682011-11-16 18:43:26 -0800869 ar.recycle();
870 }
871 // We can only have folders with >= 2 items, so we need to remove the
872 // folder and clean up if less than 2 items were included, or some
873 // failed to add, and less than 2 were actually added
874 if (folderItems.size() < 2 && folderId >= 0) {
875 // We just delete the folder and any items that made it
876 deleteId(db, folderId);
877 if (folderItems.size() > 0) {
878 deleteId(db, folderItems.get(0));
Adam Cohen228da5a2011-07-27 22:23:47 -0700879 }
Winson Chung6d092682011-11-16 18:43:26 -0800880 added = false;
Winson Chung3d503fb2011-07-13 17:25:49 -0700881 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800882 }
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700883 if (added) i++;
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700884 a.recycle();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800885 }
886 } catch (XmlPullParserException e) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800887 Log.w(TAG, "Got exception parsing favorites.", e);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800888 } catch (IOException e) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800889 Log.w(TAG, "Got exception parsing favorites.", e);
Winson Chung3d503fb2011-07-13 17:25:49 -0700890 } catch (RuntimeException e) {
891 Log.w(TAG, "Got exception parsing favorites.", e);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800892 }
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700893
894 return i;
895 }
896
Adam Cohen228da5a2011-07-27 22:23:47 -0700897 private long addAppShortcut(SQLiteDatabase db, ContentValues values, TypedArray a,
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700898 PackageManager packageManager, Intent intent) {
Adam Cohen228da5a2011-07-27 22:23:47 -0700899 long id = -1;
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700900 ActivityInfo info;
901 String packageName = a.getString(R.styleable.Favorite_packageName);
902 String className = a.getString(R.styleable.Favorite_className);
903 try {
Romain Guy693599f2010-03-23 10:58:18 -0700904 ComponentName cn;
905 try {
906 cn = new ComponentName(packageName, className);
907 info = packageManager.getActivityInfo(cn, 0);
908 } catch (PackageManager.NameNotFoundException nnfe) {
909 String[] packages = packageManager.currentToCanonicalPackageNames(
910 new String[] { packageName });
911 cn = new ComponentName(packages[0], className);
912 info = packageManager.getActivityInfo(cn, 0);
913 }
Adam Cohen228da5a2011-07-27 22:23:47 -0700914 id = generateNewId();
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700915 intent.setComponent(cn);
Romain Guy693599f2010-03-23 10:58:18 -0700916 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
917 Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
Romain Guy1ce1a242009-06-23 17:34:54 -0700918 values.put(Favorites.INTENT, intent.toUri(0));
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700919 values.put(Favorites.TITLE, info.loadLabel(packageManager).toString());
920 values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_APPLICATION);
921 values.put(Favorites.SPANX, 1);
922 values.put(Favorites.SPANY, 1);
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700923 values.put(Favorites._ID, generateNewId());
Adam Cohen228da5a2011-07-27 22:23:47 -0700924 if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values) < 0) {
925 return -1;
926 }
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700927 } catch (PackageManager.NameNotFoundException e) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800928 Log.w(TAG, "Unable to add favorite: " + packageName +
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700929 "/" + className, e);
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700930 }
Adam Cohen228da5a2011-07-27 22:23:47 -0700931 return id;
932 }
933
934 private long addFolder(SQLiteDatabase db, ContentValues values) {
935 values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_FOLDER);
936 values.put(Favorites.SPANX, 1);
937 values.put(Favorites.SPANY, 1);
938 long id = generateNewId();
939 values.put(Favorites._ID, id);
940 if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values) <= 0) {
941 return -1;
942 } else {
943 return id;
944 }
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700945 }
946
Bjorn Bringertcd8fec02010-01-14 13:26:43 +0000947 private ComponentName getSearchWidgetProvider() {
948 SearchManager searchManager =
949 (SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE);
950 ComponentName searchComponent = searchManager.getGlobalSearchActivity();
951 if (searchComponent == null) return null;
952 return getProviderInPackage(searchComponent.getPackageName());
953 }
954
955 /**
956 * Gets an appwidget provider from the given package. If the package contains more than
957 * one appwidget provider, an arbitrary one is returned.
958 */
959 private ComponentName getProviderInPackage(String packageName) {
960 AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext);
961 List<AppWidgetProviderInfo> providers = appWidgetManager.getInstalledProviders();
962 if (providers == null) return null;
963 final int providerCount = providers.size();
964 for (int i = 0; i < providerCount; i++) {
965 ComponentName provider = providers.get(i).provider;
966 if (provider != null && provider.getPackageName().equals(packageName)) {
967 return provider;
968 }
969 }
970 return null;
971 }
972
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700973 private boolean addSearchWidget(SQLiteDatabase db, ContentValues values) {
Bjorn Bringertcd8fec02010-01-14 13:26:43 +0000974 ComponentName cn = getSearchWidgetProvider();
Bjorn Bringert7984c942009-12-09 15:38:25 +0000975 return addAppWidget(db, values, cn, 4, 1);
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700976 }
977
978 private boolean addClockWidget(SQLiteDatabase db, ContentValues values) {
Bjorn Bringert34251342009-12-15 13:33:11 +0000979 ComponentName cn = new ComponentName("com.android.alarmclock",
980 "com.android.alarmclock.AnalogAppWidgetProvider");
981 return addAppWidget(db, values, cn, 2, 2);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800982 }
Adam Cohen228da5a2011-07-27 22:23:47 -0700983
Romain Guy693599f2010-03-23 10:58:18 -0700984 private boolean addAppWidget(SQLiteDatabase db, ContentValues values, TypedArray a,
985 PackageManager packageManager) {
986
Mike Cleronb87bd162009-10-30 16:36:56 -0700987 String packageName = a.getString(R.styleable.Favorite_packageName);
988 String className = a.getString(R.styleable.Favorite_className);
989
990 if (packageName == null || className == null) {
991 return false;
992 }
Romain Guy693599f2010-03-23 10:58:18 -0700993
994 boolean hasPackage = true;
Mike Cleronb87bd162009-10-30 16:36:56 -0700995 ComponentName cn = new ComponentName(packageName, className);
Romain Guy693599f2010-03-23 10:58:18 -0700996 try {
997 packageManager.getReceiverInfo(cn, 0);
998 } catch (Exception e) {
999 String[] packages = packageManager.currentToCanonicalPackageNames(
1000 new String[] { packageName });
1001 cn = new ComponentName(packages[0], className);
1002 try {
1003 packageManager.getReceiverInfo(cn, 0);
1004 } catch (Exception e1) {
1005 hasPackage = false;
1006 }
1007 }
1008
1009 if (hasPackage) {
1010 int spanX = a.getInt(R.styleable.Favorite_spanX, 0);
1011 int spanY = a.getInt(R.styleable.Favorite_spanY, 0);
1012 return addAppWidget(db, values, cn, spanX, spanY);
1013 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -07001014
Romain Guy693599f2010-03-23 10:58:18 -07001015 return false;
Bjorn Bringert7984c942009-12-09 15:38:25 +00001016 }
1017
1018 private boolean addAppWidget(SQLiteDatabase db, ContentValues values, ComponentName cn,
1019 int spanX, int spanY) {
Mike Cleronb87bd162009-10-30 16:36:56 -07001020 boolean allocatedAppWidgets = false;
1021 final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext);
1022
1023 try {
1024 int appWidgetId = mAppWidgetHost.allocateAppWidgetId();
Daniel Lehmannc3a80402012-04-23 21:35:11 -07001025
Mike Cleronb87bd162009-10-30 16:36:56 -07001026 values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_APPWIDGET);
Bjorn Bringert7984c942009-12-09 15:38:25 +00001027 values.put(Favorites.SPANX, spanX);
1028 values.put(Favorites.SPANY, spanY);
Mike Cleronb87bd162009-10-30 16:36:56 -07001029 values.put(Favorites.APPWIDGET_ID, appWidgetId);
Michael Jurkaa8c760d2011-04-28 14:59:33 -07001030 values.put(Favorites._ID, generateNewId());
1031 dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values);
Mike Cleronb87bd162009-10-30 16:36:56 -07001032
1033 allocatedAppWidgets = true;
Daniel Lehmannc3a80402012-04-23 21:35:11 -07001034
Michael Jurka8b805b12012-04-18 14:23:14 -07001035 // TODO: need to check return value
1036 appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, cn);
Mike Cleronb87bd162009-10-30 16:36:56 -07001037 } catch (RuntimeException ex) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -08001038 Log.e(TAG, "Problem allocating appWidgetId", ex);
Mike Cleronb87bd162009-10-30 16:36:56 -07001039 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -07001040
Mike Cleronb87bd162009-10-30 16:36:56 -07001041 return allocatedAppWidgets;
1042 }
Adam Cohen228da5a2011-07-27 22:23:47 -07001043
1044 private long addUriShortcut(SQLiteDatabase db, ContentValues values,
Mike Cleronb87bd162009-10-30 16:36:56 -07001045 TypedArray a) {
1046 Resources r = mContext.getResources();
1047
1048 final int iconResId = a.getResourceId(R.styleable.Favorite_icon, 0);
1049 final int titleResId = a.getResourceId(R.styleable.Favorite_title, 0);
1050
Romain Guy7eb9e5e2009-12-02 20:10:07 -08001051 Intent intent;
Mike Cleronb87bd162009-10-30 16:36:56 -07001052 String uri = null;
1053 try {
1054 uri = a.getString(R.styleable.Favorite_uri);
1055 intent = Intent.parseUri(uri, 0);
1056 } catch (URISyntaxException e) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -08001057 Log.w(TAG, "Shortcut has malformed uri: " + uri);
Adam Cohen228da5a2011-07-27 22:23:47 -07001058 return -1; // Oh well
Mike Cleronb87bd162009-10-30 16:36:56 -07001059 }
1060
1061 if (iconResId == 0 || titleResId == 0) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -08001062 Log.w(TAG, "Shortcut is missing title or icon resource ID");
Adam Cohen228da5a2011-07-27 22:23:47 -07001063 return -1;
Mike Cleronb87bd162009-10-30 16:36:56 -07001064 }
1065
Adam Cohen228da5a2011-07-27 22:23:47 -07001066 long id = generateNewId();
Mike Cleronb87bd162009-10-30 16:36:56 -07001067 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1068 values.put(Favorites.INTENT, intent.toUri(0));
1069 values.put(Favorites.TITLE, r.getString(titleResId));
1070 values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_SHORTCUT);
1071 values.put(Favorites.SPANX, 1);
1072 values.put(Favorites.SPANY, 1);
1073 values.put(Favorites.ICON_TYPE, Favorites.ICON_TYPE_RESOURCE);
1074 values.put(Favorites.ICON_PACKAGE, mContext.getPackageName());
1075 values.put(Favorites.ICON_RESOURCE, r.getResourceName(iconResId));
Adam Cohen228da5a2011-07-27 22:23:47 -07001076 values.put(Favorites._ID, id);
Mike Cleronb87bd162009-10-30 16:36:56 -07001077
Adam Cohen228da5a2011-07-27 22:23:47 -07001078 if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values) < 0) {
1079 return -1;
1080 }
1081 return id;
Mike Cleronb87bd162009-10-30 16:36:56 -07001082 }
1083 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -07001084
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001085 /**
1086 * Build a query string that will match any row where the column matches
1087 * anything in the values list.
1088 */
1089 static String buildOrWhereString(String column, int[] values) {
1090 StringBuilder selectWhere = new StringBuilder();
1091 for (int i = values.length - 1; i >= 0; i--) {
1092 selectWhere.append(column).append("=").append(values[i]);
1093 if (i > 0) {
1094 selectWhere.append(" OR ");
1095 }
1096 }
1097 return selectWhere.toString();
1098 }
1099
1100 static class SqlArguments {
1101 public final String table;
1102 public final String where;
1103 public final String[] args;
1104
1105 SqlArguments(Uri url, String where, String[] args) {
1106 if (url.getPathSegments().size() == 1) {
1107 this.table = url.getPathSegments().get(0);
1108 this.where = where;
1109 this.args = args;
1110 } else if (url.getPathSegments().size() != 2) {
1111 throw new IllegalArgumentException("Invalid URI: " + url);
1112 } else if (!TextUtils.isEmpty(where)) {
1113 throw new UnsupportedOperationException("WHERE clause not supported: " + url);
1114 } else {
1115 this.table = url.getPathSegments().get(0);
Daniel Lehmannc3a80402012-04-23 21:35:11 -07001116 this.where = "_id=" + ContentUris.parseId(url);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001117 this.args = null;
1118 }
1119 }
1120
1121 SqlArguments(Uri url) {
1122 if (url.getPathSegments().size() == 1) {
1123 table = url.getPathSegments().get(0);
1124 where = null;
1125 args = null;
1126 } else {
1127 throw new IllegalArgumentException("Invalid URI: " + url);
1128 }
1129 }
1130 }
1131}