blob: 4eb30e746f8578167ed7b5ace452c7008dd53cee [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
Daniel Sandler325dc232013-06-05 22:57:57 -040017package com.android.launcher3;
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
Daniel Sandler325dc232013-06-05 22:57:57 -040052import com.android.launcher3.LauncherSettings.Favorites;
Chris Wrene523e702013-10-09 10:36:55 -040053import com.android.launcher3.config.ProviderConfig;
Michael Jurka8b805b12012-04-18 14:23:14 -070054
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
Chris Wren1ada10d2013-09-13 18:01:38 -040069 private static final int DATABASE_VERSION = 15;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080070
Adam Cohene25af792013-06-06 23:08:25 -070071 static final String OLD_AUTHORITY = "com.android.launcher2.settings";
Chris Wrene523e702013-10-09 10:36:55 -040072 static final String AUTHORITY = ProviderConfig.AUTHORITY;
Winson Chung3d503fb2011-07-13 17:25:49 -070073
Dan Sandlerf0b8dac2013-11-19 12:21:25 -050074 // Should we attempt to load anything from the com.android.launcher2 provider?
75 static final boolean IMPORT_LAUNCHER2_DATABASE = false;
76
The Android Open Source Project31dd5032009-03-03 19:32:27 -080077 static final String TABLE_FAVORITES = "favorites";
Adam Cohendcd297f2013-06-18 13:13:40 -070078 static final String TABLE_WORKSPACE_SCREENS = "workspaceScreens";
The Android Open Source Project31dd5032009-03-03 19:32:27 -080079 static final String PARAMETER_NOTIFY = "notify";
Winson Chungc763c4e2013-07-19 13:49:06 -070080 static final String UPGRADED_FROM_OLD_DATABASE =
81 "UPGRADED_FROM_OLD_DATABASE";
82 static final String EMPTY_DATABASE_CREATED =
83 "EMPTY_DATABASE_CREATED";
Michael Jurka45355c42012-10-08 13:21:35 +020084 static final String DEFAULT_WORKSPACE_RESOURCE_ID =
85 "DEFAULT_WORKSPACE_RESOURCE_ID";
The Android Open Source Project31dd5032009-03-03 19:32:27 -080086
Winson Chungb3302ae2012-05-01 10:19:14 -070087 private static final String ACTION_APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE =
Adam Cohene25af792013-06-06 23:08:25 -070088 "com.android.launcher.action.APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE";
Winson Chungb3302ae2012-05-01 10:19:14 -070089
Jeffrey Sharkey2bbcae12009-03-31 14:37:57 -070090 /**
Romain Guy73b979d2009-06-09 12:57:21 -070091 * {@link Uri} triggered at any registered {@link android.database.ContentObserver} when
Jeffrey Sharkey2bbcae12009-03-31 14:37:57 -070092 * {@link AppWidgetHost#deleteHost()} is called during database creation.
93 * Use this to recall {@link AppWidgetHost#startListening()} if needed.
94 */
95 static final Uri CONTENT_APPWIDGET_RESET_URI =
96 Uri.parse("content://" + AUTHORITY + "/appWidgetReset");
Daniel Lehmannc3a80402012-04-23 21:35:11 -070097
Michael Jurkaa8c760d2011-04-28 14:59:33 -070098 private DatabaseHelper mOpenHelper;
Winson Chungc763c4e2013-07-19 13:49:06 -070099 private static boolean sJustLoadedFromOldDb;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800100
101 @Override
102 public boolean onCreate() {
Daniel Sandlere4f98912013-06-25 15:13:26 -0400103 final Context context = getContext();
104 mOpenHelper = new DatabaseHelper(context);
105 LauncherAppState.setLauncherProvider(this);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800106 return true;
107 }
108
109 @Override
110 public String getType(Uri uri) {
111 SqlArguments args = new SqlArguments(uri, null, null);
112 if (TextUtils.isEmpty(args.where)) {
113 return "vnd.android.cursor.dir/" + args.table;
114 } else {
115 return "vnd.android.cursor.item/" + args.table;
116 }
117 }
118
119 @Override
120 public Cursor query(Uri uri, String[] projection, String selection,
121 String[] selectionArgs, String sortOrder) {
122
123 SqlArguments args = new SqlArguments(uri, selection, selectionArgs);
124 SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
125 qb.setTables(args.table);
126
Romain Guy73b979d2009-06-09 12:57:21 -0700127 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800128 Cursor result = qb.query(db, projection, args.where, args.args, null, null, sortOrder);
129 result.setNotificationUri(getContext().getContentResolver(), uri);
130
131 return result;
132 }
133
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700134 private static long dbInsertAndCheck(DatabaseHelper helper,
135 SQLiteDatabase db, String table, String nullColumnHack, ContentValues values) {
136 if (!values.containsKey(LauncherSettings.Favorites._ID)) {
137 throw new RuntimeException("Error: attempting to add item without specifying an id");
138 }
139 return db.insert(table, nullColumnHack, values);
140 }
141
Adam Cohen228da5a2011-07-27 22:23:47 -0700142 private static void deleteId(SQLiteDatabase db, long id) {
143 Uri uri = LauncherSettings.Favorites.getContentUri(id, false);
144 SqlArguments args = new SqlArguments(uri, null, null);
145 db.delete(args.table, args.where, args.args);
146 }
147
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800148 @Override
149 public Uri insert(Uri uri, ContentValues initialValues) {
150 SqlArguments args = new SqlArguments(uri);
151
152 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
Chris Wren1ada10d2013-09-13 18:01:38 -0400153 addModifiedTime(initialValues);
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700154 final long rowId = dbInsertAndCheck(mOpenHelper, db, args.table, null, initialValues);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800155 if (rowId <= 0) return null;
156
157 uri = ContentUris.withAppendedId(uri, rowId);
158 sendNotify(uri);
159
160 return uri;
161 }
162
163 @Override
164 public int bulkInsert(Uri uri, ContentValues[] values) {
165 SqlArguments args = new SqlArguments(uri);
166
167 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
168 db.beginTransaction();
169 try {
170 int numValues = values.length;
171 for (int i = 0; i < numValues; i++) {
Chris Wren1ada10d2013-09-13 18:01:38 -0400172 addModifiedTime(values[i]);
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700173 if (dbInsertAndCheck(mOpenHelper, db, args.table, null, values[i]) < 0) {
174 return 0;
175 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800176 }
177 db.setTransactionSuccessful();
178 } finally {
179 db.endTransaction();
180 }
181
182 sendNotify(uri);
183 return values.length;
184 }
185
186 @Override
187 public int delete(Uri uri, String selection, String[] selectionArgs) {
188 SqlArguments args = new SqlArguments(uri, selection, selectionArgs);
189
190 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
191 int count = db.delete(args.table, args.where, args.args);
192 if (count > 0) sendNotify(uri);
193
194 return count;
195 }
196
197 @Override
198 public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
199 SqlArguments args = new SqlArguments(uri, selection, selectionArgs);
200
Chris Wren1ada10d2013-09-13 18:01:38 -0400201 addModifiedTime(values);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800202 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
203 int count = db.update(args.table, values, args.where, args.args);
204 if (count > 0) sendNotify(uri);
205
206 return count;
207 }
208
209 private void sendNotify(Uri uri) {
210 String notify = uri.getQueryParameter(PARAMETER_NOTIFY);
211 if (notify == null || "true".equals(notify)) {
212 getContext().getContentResolver().notifyChange(uri, null);
213 }
Chris Wren1ada10d2013-09-13 18:01:38 -0400214
215 // always notify the backup agent
Chris Wren92aa4232013-10-04 11:29:36 -0400216 LauncherBackupAgentHelper.dataChanged(getContext());
Chris Wren1ada10d2013-09-13 18:01:38 -0400217 }
218
219 private void addModifiedTime(ContentValues values) {
220 values.put(LauncherSettings.ChangeLogColumns.MODIFIED, System.currentTimeMillis());
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800221 }
222
Adam Cohendcd297f2013-06-18 13:13:40 -0700223 public long generateNewItemId() {
224 return mOpenHelper.generateNewItemId();
225 }
226
Winson Chungc763c4e2013-07-19 13:49:06 -0700227 public void updateMaxItemId(long id) {
228 mOpenHelper.updateMaxItemId(id);
229 }
230
Adam Cohendcd297f2013-06-18 13:13:40 -0700231 public long generateNewScreenId() {
232 return mOpenHelper.generateNewScreenId();
233 }
234
235 // This is only required one time while loading the workspace during the
236 // upgrade path, and should never be called from anywhere else.
237 public void updateMaxScreenId(long maxScreenId) {
238 mOpenHelper.updateMaxScreenId(maxScreenId);
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700239 }
240
Brian Muramatsu5524b492012-10-02 16:55:54 -0700241 /**
Adam Cohene25af792013-06-06 23:08:25 -0700242 * @param Should we load the old db for upgrade? first run only.
243 */
Winson Chungc763c4e2013-07-19 13:49:06 -0700244 synchronized public boolean justLoadedOldDb() {
Daniel Sandlercc8befa2013-06-11 14:45:48 -0400245 String spKey = LauncherAppState.getSharedPreferencesKey();
Adam Cohene25af792013-06-06 23:08:25 -0700246 SharedPreferences sp = getContext().getSharedPreferences(spKey, Context.MODE_PRIVATE);
247
Winson Chungc763c4e2013-07-19 13:49:06 -0700248 boolean loadedOldDb = false || sJustLoadedFromOldDb;
Adam Cohendcd297f2013-06-18 13:13:40 -0700249
Winson Chungc763c4e2013-07-19 13:49:06 -0700250 sJustLoadedFromOldDb = false;
251 if (sp.getBoolean(UPGRADED_FROM_OLD_DATABASE, false)) {
Adam Cohene25af792013-06-06 23:08:25 -0700252
253 SharedPreferences.Editor editor = sp.edit();
Winson Chungc763c4e2013-07-19 13:49:06 -0700254 editor.remove(UPGRADED_FROM_OLD_DATABASE);
Adam Cohene25af792013-06-06 23:08:25 -0700255 editor.commit();
Winson Chungc763c4e2013-07-19 13:49:06 -0700256 loadedOldDb = true;
Adam Cohene25af792013-06-06 23:08:25 -0700257 }
Winson Chungc763c4e2013-07-19 13:49:06 -0700258 return loadedOldDb;
Adam Cohene25af792013-06-06 23:08:25 -0700259 }
260
261 /**
Brian Muramatsu5524b492012-10-02 16:55:54 -0700262 * @param workspaceResId that can be 0 to use default or non-zero for specific resource
263 */
Michael Jurka45355c42012-10-08 13:21:35 +0200264 synchronized public void loadDefaultFavoritesIfNecessary(int origWorkspaceResId) {
Daniel Sandlercc8befa2013-06-11 14:45:48 -0400265 String spKey = LauncherAppState.getSharedPreferencesKey();
Michael Jurkab85f8a42012-04-25 15:48:32 -0700266 SharedPreferences sp = getContext().getSharedPreferences(spKey, Context.MODE_PRIVATE);
Adam Cohene25af792013-06-06 23:08:25 -0700267
Winson Chungc763c4e2013-07-19 13:49:06 -0700268 if (sp.getBoolean(EMPTY_DATABASE_CREATED, false)) {
Michael Jurka45355c42012-10-08 13:21:35 +0200269 int workspaceResId = origWorkspaceResId;
270
Brian Muramatsu5524b492012-10-02 16:55:54 -0700271 // Use default workspace resource if none provided
272 if (workspaceResId == 0) {
Michael Jurka45355c42012-10-08 13:21:35 +0200273 workspaceResId = sp.getInt(DEFAULT_WORKSPACE_RESOURCE_ID, R.xml.default_workspace);
Brian Muramatsu5524b492012-10-02 16:55:54 -0700274 }
275
Michael Jurkab85f8a42012-04-25 15:48:32 -0700276 // Populate favorites table with initial favorites
277 SharedPreferences.Editor editor = sp.edit();
Winson Chungc763c4e2013-07-19 13:49:06 -0700278 editor.remove(EMPTY_DATABASE_CREATED);
Michael Jurka45355c42012-10-08 13:21:35 +0200279 if (origWorkspaceResId != 0) {
280 editor.putInt(DEFAULT_WORKSPACE_RESOURCE_ID, origWorkspaceResId);
281 }
Adam Cohene25af792013-06-06 23:08:25 -0700282
Brian Muramatsu5524b492012-10-02 16:55:54 -0700283 mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(), workspaceResId);
Winson Chungc763c4e2013-07-19 13:49:06 -0700284 mOpenHelper.setFlagJustLoadedOldDb();
Michael Jurkab85f8a42012-04-25 15:48:32 -0700285 editor.commit();
286 }
287 }
288
Winson Chungc763c4e2013-07-19 13:49:06 -0700289 private static interface ContentValuesCallback {
290 public void onRow(ContentValues values);
291 }
292
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800293 private static class DatabaseHelper extends SQLiteOpenHelper {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800294 private static final String TAG_FAVORITES = "favorites";
295 private static final String TAG_FAVORITE = "favorite";
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700296 private static final String TAG_CLOCK = "clock";
297 private static final String TAG_SEARCH = "search";
Mike Cleronb87bd162009-10-30 16:36:56 -0700298 private static final String TAG_APPWIDGET = "appwidget";
299 private static final String TAG_SHORTCUT = "shortcut";
Adam Cohen228da5a2011-07-27 22:23:47 -0700300 private static final String TAG_FOLDER = "folder";
Winson Chungb3302ae2012-05-01 10:19:14 -0700301 private static final String TAG_EXTRA = "extra";
Daniel Sandler57dac262013-10-03 13:28:36 -0400302 private static final String TAG_INCLUDE = "include";
Winson Chung3d503fb2011-07-13 17:25:49 -0700303
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800304 private final Context mContext;
The Android Open Source Project7376fae2009-03-11 12:11:58 -0700305 private final AppWidgetHost mAppWidgetHost;
Adam Cohendcd297f2013-06-18 13:13:40 -0700306 private long mMaxItemId = -1;
307 private long mMaxScreenId = -1;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800308
309 DatabaseHelper(Context context) {
310 super(context, DATABASE_NAME, null, DATABASE_VERSION);
311 mContext = context;
The Android Open Source Project7376fae2009-03-11 12:11:58 -0700312 mAppWidgetHost = new AppWidgetHost(context, Launcher.APPWIDGET_HOST_ID);
Winson Chung3d503fb2011-07-13 17:25:49 -0700313
314 // In the case where neither onCreate nor onUpgrade gets called, we read the maxId from
315 // the DB here
Adam Cohendcd297f2013-06-18 13:13:40 -0700316 if (mMaxItemId == -1) {
317 mMaxItemId = initializeMaxItemId(getWritableDatabase());
318 }
319 if (mMaxScreenId == -1) {
320 mMaxScreenId = initializeMaxScreenId(getWritableDatabase());
Winson Chung3d503fb2011-07-13 17:25:49 -0700321 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800322 }
323
Jeffrey Sharkey2bbcae12009-03-31 14:37:57 -0700324 /**
325 * Send notification that we've deleted the {@link AppWidgetHost},
326 * probably as part of the initial database creation. The receiver may
327 * want to re-call {@link AppWidgetHost#startListening()} to ensure
328 * callbacks are correctly set.
329 */
330 private void sendAppWidgetResetNotify() {
331 final ContentResolver resolver = mContext.getContentResolver();
332 resolver.notifyChange(CONTENT_APPWIDGET_RESET_URI, null);
333 }
334
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800335 @Override
336 public void onCreate(SQLiteDatabase db) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800337 if (LOGD) Log.d(TAG, "creating new launcher database");
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700338
Adam Cohendcd297f2013-06-18 13:13:40 -0700339 mMaxItemId = 1;
340 mMaxScreenId = 0;
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700341
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800342 db.execSQL("CREATE TABLE favorites (" +
343 "_id INTEGER PRIMARY KEY," +
344 "title TEXT," +
345 "intent TEXT," +
346 "container INTEGER," +
347 "screen INTEGER," +
348 "cellX INTEGER," +
349 "cellY INTEGER," +
350 "spanX INTEGER," +
351 "spanY INTEGER," +
352 "itemType INTEGER," +
The Android Open Source Projectca9475f2009-03-13 13:04:24 -0700353 "appWidgetId INTEGER NOT NULL DEFAULT -1," +
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800354 "isShortcut INTEGER," +
355 "iconType INTEGER," +
356 "iconPackage TEXT," +
357 "iconResource TEXT," +
358 "icon BLOB," +
359 "uri TEXT," +
Chris Wrend5e66bf2013-09-16 14:02:29 -0400360 "displayMode INTEGER," +
Chris Wren1ada10d2013-09-13 18:01:38 -0400361 "appWidgetProvider TEXT," +
362 "modified INTEGER NOT NULL DEFAULT 0" +
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800363 ");");
Adam Cohendcd297f2013-06-18 13:13:40 -0700364 addWorkspacesTable(db);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800365
The Android Open Source Project7376fae2009-03-11 12:11:58 -0700366 // Database was just created, so wipe any previous widgets
367 if (mAppWidgetHost != null) {
368 mAppWidgetHost.deleteHost();
Jeffrey Sharkey2bbcae12009-03-31 14:37:57 -0700369 sendAppWidgetResetNotify();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800370 }
Winson Chung3d503fb2011-07-13 17:25:49 -0700371
Dan Sandlerf0b8dac2013-11-19 12:21:25 -0500372 if (IMPORT_LAUNCHER2_DATABASE) {
373 // Try converting the old database
374 ContentValuesCallback permuteScreensCb = new ContentValuesCallback() {
375 public void onRow(ContentValues values) {
376 int container = values.getAsInteger(LauncherSettings.Favorites.CONTAINER);
377 if (container == Favorites.CONTAINER_DESKTOP) {
378 int screen = values.getAsInteger(LauncherSettings.Favorites.SCREEN);
379 screen = (int) upgradeLauncherDb_permuteScreens(screen);
380 values.put(LauncherSettings.Favorites.SCREEN, screen);
381 }
382 }
383 };
384 Uri uri = Uri.parse("content://" + Settings.AUTHORITY +
385 "/old_favorites?notify=true");
386 if (!convertDatabase(db, uri, permuteScreensCb, true)) {
387 // Try and upgrade from the Launcher2 db
388 uri = LauncherSettings.Favorites.OLD_CONTENT_URI;
389 if (!convertDatabase(db, uri, permuteScreensCb, false)) {
390 // If we fail, then set a flag to load the default workspace
391 setFlagEmptyDbCreated();
392 return;
Winson Chungc763c4e2013-07-19 13:49:06 -0700393 }
394 }
Dan Sandlerf0b8dac2013-11-19 12:21:25 -0500395 // Right now, in non-default workspace cases, we want to run the final
396 // upgrade code (ie. to fix workspace screen indices -> ids, etc.), so
397 // set that flag too.
398 setFlagJustLoadedOldDb();
399 } else {
400 // Fresh and clean launcher DB.
401 mMaxItemId = initializeMaxItemId(db);
402 setFlagEmptyDbCreated();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800403 }
404 }
405
Adam Cohendcd297f2013-06-18 13:13:40 -0700406 private void addWorkspacesTable(SQLiteDatabase db) {
407 db.execSQL("CREATE TABLE " + TABLE_WORKSPACE_SCREENS + " (" +
408 LauncherSettings.WorkspaceScreens._ID + " INTEGER," +
Chris Wren1ada10d2013-09-13 18:01:38 -0400409 LauncherSettings.WorkspaceScreens.SCREEN_RANK + " INTEGER," +
410 LauncherSettings.ChangeLogColumns.MODIFIED + " INTEGER NOT NULL DEFAULT 0" +
Adam Cohendcd297f2013-06-18 13:13:40 -0700411 ");");
412 }
413
Winson Chungc763c4e2013-07-19 13:49:06 -0700414 private void setFlagJustLoadedOldDb() {
Daniel Sandlercc8befa2013-06-11 14:45:48 -0400415 String spKey = LauncherAppState.getSharedPreferencesKey();
Michael Jurkab85f8a42012-04-25 15:48:32 -0700416 SharedPreferences sp = mContext.getSharedPreferences(spKey, Context.MODE_PRIVATE);
417 SharedPreferences.Editor editor = sp.edit();
Winson Chungc763c4e2013-07-19 13:49:06 -0700418 editor.putBoolean(UPGRADED_FROM_OLD_DATABASE, true);
419 editor.putBoolean(EMPTY_DATABASE_CREATED, false);
Michael Jurkab85f8a42012-04-25 15:48:32 -0700420 editor.commit();
421 }
422
Winson Chungc763c4e2013-07-19 13:49:06 -0700423 private void setFlagEmptyDbCreated() {
424 String spKey = LauncherAppState.getSharedPreferencesKey();
425 SharedPreferences sp = mContext.getSharedPreferences(spKey, Context.MODE_PRIVATE);
426 SharedPreferences.Editor editor = sp.edit();
427 editor.putBoolean(EMPTY_DATABASE_CREATED, true);
428 editor.putBoolean(UPGRADED_FROM_OLD_DATABASE, false);
429 editor.commit();
430 }
431
432 // We rearrange the screens from the old launcher
433 // 12345 -> 34512
434 private long upgradeLauncherDb_permuteScreens(long screen) {
435 if (screen >= 2) {
436 return screen - 2;
437 } else {
438 return screen + 3;
439 }
440 }
441
442 private boolean convertDatabase(SQLiteDatabase db, Uri uri,
443 ContentValuesCallback cb, boolean deleteRows) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800444 if (LOGD) Log.d(TAG, "converting database from an older format, but not onUpgrade");
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800445 boolean converted = false;
446
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800447 final ContentResolver resolver = mContext.getContentResolver();
448 Cursor cursor = null;
449
450 try {
451 cursor = resolver.query(uri, null, null, null, null);
452 } catch (Exception e) {
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700453 // Ignore
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800454 }
455
456 // We already have a favorites database in the old provider
Winson Chungc763c4e2013-07-19 13:49:06 -0700457 if (cursor != null) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800458 try {
Winson Chungc763c4e2013-07-19 13:49:06 -0700459 if (cursor.getCount() > 0) {
460 converted = copyFromCursor(db, cursor, cb) > 0;
461 if (converted && deleteRows) {
462 resolver.delete(uri, null, null);
463 }
464 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800465 } finally {
466 cursor.close();
467 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800468 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700469
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800470 if (converted) {
The Android Open Source Project7376fae2009-03-11 12:11:58 -0700471 // Convert widgets from this import into widgets
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800472 if (LOGD) Log.d(TAG, "converted and now triggering widget upgrade");
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800473 convertWidgets(db);
Winson Chungc763c4e2013-07-19 13:49:06 -0700474
475 // Update max item id
476 mMaxItemId = initializeMaxItemId(db);
477 if (LOGD) Log.d(TAG, "mMaxItemId: " + mMaxItemId);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800478 }
479
480 return converted;
481 }
482
Winson Chungc763c4e2013-07-19 13:49:06 -0700483 private int copyFromCursor(SQLiteDatabase db, Cursor c, ContentValuesCallback cb) {
Romain Guy73b979d2009-06-09 12:57:21 -0700484 final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800485 final int intentIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.INTENT);
486 final int titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE);
487 final int iconTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_TYPE);
488 final int iconIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON);
489 final int iconPackageIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_PACKAGE);
490 final int iconResourceIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_RESOURCE);
491 final int containerIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER);
492 final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE);
493 final int screenIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN);
494 final int cellXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX);
495 final int cellYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY);
496 final int uriIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.URI);
497 final int displayModeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.DISPLAY_MODE);
498
499 ContentValues[] rows = new ContentValues[c.getCount()];
500 int i = 0;
501 while (c.moveToNext()) {
502 ContentValues values = new ContentValues(c.getColumnCount());
Romain Guy73b979d2009-06-09 12:57:21 -0700503 values.put(LauncherSettings.Favorites._ID, c.getLong(idIndex));
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800504 values.put(LauncherSettings.Favorites.INTENT, c.getString(intentIndex));
505 values.put(LauncherSettings.Favorites.TITLE, c.getString(titleIndex));
506 values.put(LauncherSettings.Favorites.ICON_TYPE, c.getInt(iconTypeIndex));
507 values.put(LauncherSettings.Favorites.ICON, c.getBlob(iconIndex));
508 values.put(LauncherSettings.Favorites.ICON_PACKAGE, c.getString(iconPackageIndex));
509 values.put(LauncherSettings.Favorites.ICON_RESOURCE, c.getString(iconResourceIndex));
510 values.put(LauncherSettings.Favorites.CONTAINER, c.getInt(containerIndex));
511 values.put(LauncherSettings.Favorites.ITEM_TYPE, c.getInt(itemTypeIndex));
The Android Open Source Project7376fae2009-03-11 12:11:58 -0700512 values.put(LauncherSettings.Favorites.APPWIDGET_ID, -1);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800513 values.put(LauncherSettings.Favorites.SCREEN, c.getInt(screenIndex));
514 values.put(LauncherSettings.Favorites.CELLX, c.getInt(cellXIndex));
515 values.put(LauncherSettings.Favorites.CELLY, c.getInt(cellYIndex));
516 values.put(LauncherSettings.Favorites.URI, c.getString(uriIndex));
517 values.put(LauncherSettings.Favorites.DISPLAY_MODE, c.getInt(displayModeIndex));
Winson Chungc763c4e2013-07-19 13:49:06 -0700518 if (cb != null) {
519 cb.onRow(values);
520 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800521 rows[i++] = values;
522 }
523
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800524 int total = 0;
Winson Chungc763c4e2013-07-19 13:49:06 -0700525 if (i > 0) {
526 db.beginTransaction();
527 try {
528 int numValues = rows.length;
529 for (i = 0; i < numValues; i++) {
530 if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, rows[i]) < 0) {
531 return 0;
532 } else {
533 total++;
534 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800535 }
Winson Chungc763c4e2013-07-19 13:49:06 -0700536 db.setTransactionSuccessful();
537 } finally {
538 db.endTransaction();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800539 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800540 }
541
542 return total;
543 }
544
545 @Override
546 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Winson Chungc763c4e2013-07-19 13:49:06 -0700547 if (LOGD) Log.d(TAG, "onUpgrade triggered: " + oldVersion);
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700548
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800549 int version = oldVersion;
The Android Open Source Projectca9475f2009-03-13 13:04:24 -0700550 if (version < 3) {
551 // upgrade 1,2 -> 3 added appWidgetId column
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800552 db.beginTransaction();
553 try {
The Android Open Source Project7376fae2009-03-11 12:11:58 -0700554 // Insert new column for holding appWidgetIds
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800555 db.execSQL("ALTER TABLE favorites " +
The Android Open Source Projectca9475f2009-03-13 13:04:24 -0700556 "ADD COLUMN appWidgetId INTEGER NOT NULL DEFAULT -1;");
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800557 db.setTransactionSuccessful();
The Android Open Source Projectca9475f2009-03-13 13:04:24 -0700558 version = 3;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800559 } catch (SQLException ex) {
560 // Old version remains, which means we wipe old data
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800561 Log.e(TAG, ex.getMessage(), ex);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800562 } finally {
563 db.endTransaction();
564 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700565
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800566 // Convert existing widgets only if table upgrade was successful
The Android Open Source Projectca9475f2009-03-13 13:04:24 -0700567 if (version == 3) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800568 convertWidgets(db);
569 }
570 }
Romain Guy73b979d2009-06-09 12:57:21 -0700571
572 if (version < 4) {
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800573 version = 4;
Romain Guy73b979d2009-06-09 12:57:21 -0700574 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700575
Romain Guy509cd6a2010-03-23 15:10:56 -0700576 // Where's version 5?
577 // - Donut and sholes on 2.0 shipped with version 4 of launcher1.
Daniel Sandler325dc232013-06-05 22:57:57 -0400578 // - Passion shipped on 2.1 with version 6 of launcher3
Romain Guy509cd6a2010-03-23 15:10:56 -0700579 // - Sholes shipped on 2.1r1 (aka Mr. 3) with version 5 of launcher 1
580 // but version 5 on there was the updateContactsShortcuts change
581 // which was version 6 in launcher 2 (first shipped on passion 2.1r1).
582 // The updateContactsShortcuts change is idempotent, so running it twice
583 // is okay so we'll do that when upgrading the devices that shipped with it.
584 if (version < 6) {
Mike Cleron3a2b3f22009-11-05 17:17:42 -0800585 // We went from 3 to 5 screens. Move everything 1 to the right
586 db.beginTransaction();
587 try {
588 db.execSQL("UPDATE favorites SET screen=(screen + 1);");
589 db.setTransactionSuccessful();
Mike Cleron3a2b3f22009-11-05 17:17:42 -0800590 } catch (SQLException ex) {
591 // Old version remains, which means we wipe old data
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800592 Log.e(TAG, ex.getMessage(), ex);
Mike Cleron3a2b3f22009-11-05 17:17:42 -0800593 } finally {
594 db.endTransaction();
595 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700596
Romain Guy509cd6a2010-03-23 15:10:56 -0700597 // We added the fast track.
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800598 if (updateContactsShortcuts(db)) {
599 version = 6;
600 }
601 }
Bjorn Bringert7984c942009-12-09 15:38:25 +0000602
603 if (version < 7) {
604 // Version 7 gets rid of the special search widget.
605 convertWidgets(db);
606 version = 7;
607 }
608
Joe Onorato0589f0f2010-02-08 13:44:00 -0800609 if (version < 8) {
610 // Version 8 (froyo) has the icons all normalized. This should
611 // already be the case in practice, but we now rely on it and don't
612 // resample the images each time.
613 normalizeIcons(db);
614 version = 8;
615 }
616
Winson Chung3d503fb2011-07-13 17:25:49 -0700617 if (version < 9) {
618 // The max id is not yet set at this point (onUpgrade is triggered in the ctor
619 // before it gets a change to get set, so we need to read it here when we use it)
Adam Cohendcd297f2013-06-18 13:13:40 -0700620 if (mMaxItemId == -1) {
621 mMaxItemId = initializeMaxItemId(db);
Winson Chung3d503fb2011-07-13 17:25:49 -0700622 }
623
624 // Add default hotseat icons
Winson Chung6d092682011-11-16 18:43:26 -0800625 loadFavorites(db, R.xml.update_workspace);
Winson Chung3d503fb2011-07-13 17:25:49 -0700626 version = 9;
627 }
628
Daniel Lehmannd02402c2012-05-14 18:30:53 -0700629 // We bumped the version three time during JB, once to update the launch flags, once to
630 // update the override for the default launch animation and once to set the mimetype
631 // to improve startup performance
632 if (version < 12) {
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700633 // Contact shortcuts need a different set of flags to be launched now
634 // The updateContactsShortcuts change is idempotent, so we can keep using it like
635 // back in the Donut days
636 updateContactsShortcuts(db);
Daniel Lehmannd02402c2012-05-14 18:30:53 -0700637 version = 12;
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700638 }
639
Adam Cohendcd297f2013-06-18 13:13:40 -0700640 if (version < 13) {
641 // With the new shrink-wrapped and re-orderable workspaces, it makes sense
642 // to persist workspace screens and their relative order.
643 mMaxScreenId = 0;
644
645 // This will never happen in the wild, but when we switch to using workspace
646 // screen ids, redo the import from old launcher.
Winson Chungc763c4e2013-07-19 13:49:06 -0700647 sJustLoadedFromOldDb = true;
Adam Cohendcd297f2013-06-18 13:13:40 -0700648
649 addWorkspacesTable(db);
650 version = 13;
651 }
652
Chris Wrend5e66bf2013-09-16 14:02:29 -0400653 if (version < 14) {
654 db.beginTransaction();
655 try {
656 // Insert new column for holding widget provider name
657 db.execSQL("ALTER TABLE favorites " +
658 "ADD COLUMN appWidgetProvider TEXT;");
659 db.setTransactionSuccessful();
660 version = 14;
661 } catch (SQLException ex) {
662 // Old version remains, which means we wipe old data
663 Log.e(TAG, ex.getMessage(), ex);
664 } finally {
665 db.endTransaction();
666 }
667 }
668
Chris Wren1ada10d2013-09-13 18:01:38 -0400669
670 if (version < 15) {
671 db.beginTransaction();
672 try {
673 // Insert new column for holding update timestamp
674 db.execSQL("ALTER TABLE favorites " +
675 "ADD COLUMN modified INTEGER NOT NULL DEFAULT 0;");
676 db.execSQL("ALTER TABLE workspaceScreens " +
677 "ADD COLUMN modified INTEGER NOT NULL DEFAULT 0;");
678 db.setTransactionSuccessful();
679 version = 15;
680 } catch (SQLException ex) {
681 // Old version remains, which means we wipe old data
682 Log.e(TAG, ex.getMessage(), ex);
683 } finally {
684 db.endTransaction();
685 }
686 }
687
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800688 if (version != DATABASE_VERSION) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800689 Log.w(TAG, "Destroying all old data.");
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800690 db.execSQL("DROP TABLE IF EXISTS " + TABLE_FAVORITES);
Adam Cohendcd297f2013-06-18 13:13:40 -0700691 db.execSQL("DROP TABLE IF EXISTS " + TABLE_WORKSPACE_SCREENS);
692
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800693 onCreate(db);
694 }
695 }
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800696
697 private boolean updateContactsShortcuts(SQLiteDatabase db) {
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800698 final String selectWhere = buildOrWhereString(Favorites.ITEM_TYPE,
699 new int[] { Favorites.ITEM_TYPE_SHORTCUT });
700
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700701 Cursor c = null;
702 final String actionQuickContact = "com.android.contacts.action.QUICK_CONTACT";
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800703 db.beginTransaction();
704 try {
705 // Select and iterate through each matching widget
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700706 c = db.query(TABLE_FAVORITES,
707 new String[] { Favorites._ID, Favorites.INTENT },
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800708 selectWhere, null, null, null, null);
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700709 if (c == null) return false;
710
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800711 if (LOGD) Log.d(TAG, "found upgrade cursor count=" + c.getCount());
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700712
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800713 final int idIndex = c.getColumnIndex(Favorites._ID);
714 final int intentIndex = c.getColumnIndex(Favorites.INTENT);
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700715
716 while (c.moveToNext()) {
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800717 long favoriteId = c.getLong(idIndex);
718 final String intentUri = c.getString(intentIndex);
719 if (intentUri != null) {
720 try {
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700721 final Intent intent = Intent.parseUri(intentUri, 0);
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800722 android.util.Log.d("Home", intent.toString());
723 final Uri uri = intent.getData();
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700724 if (uri != null) {
725 final String data = uri.toString();
726 if ((Intent.ACTION_VIEW.equals(intent.getAction()) ||
727 actionQuickContact.equals(intent.getAction())) &&
728 (data.startsWith("content://contacts/people/") ||
729 data.startsWith("content://com.android.contacts/" +
730 "contacts/lookup/"))) {
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800731
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700732 final Intent newIntent = new Intent(actionQuickContact);
733 // When starting from the launcher, start in a new, cleared task
734 // CLEAR_WHEN_TASK_RESET cannot reset the root of a task, so we
735 // clear the whole thing preemptively here since
736 // QuickContactActivity will finish itself when launching other
737 // detail activities.
738 newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
739 Intent.FLAG_ACTIVITY_CLEAR_TASK);
Winson Chung2672ff92012-05-04 16:22:30 -0700740 newIntent.putExtra(
741 Launcher.INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION, true);
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700742 newIntent.setData(uri);
Daniel Lehmannd02402c2012-05-14 18:30:53 -0700743 // Determine the type and also put that in the shortcut
744 // (that can speed up launch a bit)
745 newIntent.setDataAndType(uri, newIntent.resolveType(mContext));
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800746
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700747 final ContentValues values = new ContentValues();
748 values.put(LauncherSettings.Favorites.INTENT,
749 newIntent.toUri(0));
750
751 String updateWhere = Favorites._ID + "=" + favoriteId;
752 db.update(TABLE_FAVORITES, values, updateWhere, null);
753 }
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800754 }
755 } catch (RuntimeException ex) {
756 Log.e(TAG, "Problem upgrading shortcut", ex);
757 } catch (URISyntaxException e) {
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700758 Log.e(TAG, "Problem upgrading shortcut", e);
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800759 }
760 }
761 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700762
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800763 db.setTransactionSuccessful();
764 } catch (SQLException ex) {
765 Log.w(TAG, "Problem while upgrading contacts", ex);
766 return false;
767 } finally {
768 db.endTransaction();
769 if (c != null) {
770 c.close();
771 }
772 }
773
774 return true;
775 }
776
Joe Onorato0589f0f2010-02-08 13:44:00 -0800777 private void normalizeIcons(SQLiteDatabase db) {
778 Log.d(TAG, "normalizing icons");
779
Joe Onorato346e1292010-02-18 10:34:24 -0500780 db.beginTransaction();
Joe Onorato0589f0f2010-02-08 13:44:00 -0800781 Cursor c = null;
Joe Onorato9690b392010-03-23 17:34:37 -0400782 SQLiteStatement update = null;
Joe Onorato0589f0f2010-02-08 13:44:00 -0800783 try {
784 boolean logged = false;
Joe Onorato9690b392010-03-23 17:34:37 -0400785 update = db.compileStatement("UPDATE favorites "
Jeff Hamiltoneaf77d62010-02-13 00:08:17 -0600786 + "SET icon=? WHERE _id=?");
Joe Onorato0589f0f2010-02-08 13:44:00 -0800787
788 c = db.rawQuery("SELECT _id, icon FROM favorites WHERE iconType=" +
789 Favorites.ICON_TYPE_BITMAP, null);
790
791 final int idIndex = c.getColumnIndexOrThrow(Favorites._ID);
792 final int iconIndex = c.getColumnIndexOrThrow(Favorites.ICON);
793
794 while (c.moveToNext()) {
795 long id = c.getLong(idIndex);
796 byte[] data = c.getBlob(iconIndex);
797 try {
798 Bitmap bitmap = Utilities.resampleIconBitmap(
799 BitmapFactory.decodeByteArray(data, 0, data.length),
800 mContext);
801 if (bitmap != null) {
802 update.bindLong(1, id);
803 data = ItemInfo.flattenBitmap(bitmap);
804 if (data != null) {
805 update.bindBlob(2, data);
806 update.execute();
807 }
808 bitmap.recycle();
Joe Onorato0589f0f2010-02-08 13:44:00 -0800809 }
810 } catch (Exception e) {
811 if (!logged) {
812 Log.e(TAG, "Failed normalizing icon " + id, e);
813 } else {
814 Log.e(TAG, "Also failed normalizing icon " + id);
815 }
816 logged = true;
817 }
818 }
Bjorn Bringert3a928e42010-02-19 11:15:40 +0000819 db.setTransactionSuccessful();
Joe Onorato0589f0f2010-02-08 13:44:00 -0800820 } catch (SQLException ex) {
821 Log.w(TAG, "Problem while allocating appWidgetIds for existing widgets", ex);
822 } finally {
823 db.endTransaction();
Joe Onorato9690b392010-03-23 17:34:37 -0400824 if (update != null) {
825 update.close();
826 }
Joe Onorato0589f0f2010-02-08 13:44:00 -0800827 if (c != null) {
828 c.close();
829 }
830 }
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700831 }
832
833 // Generates a new ID to use for an object in your database. This method should be only
834 // called from the main UI thread. As an exception, we do call it when we call the
835 // constructor from the worker thread; however, this doesn't extend until after the
836 // constructor is called, and we only pass a reference to LauncherProvider to LauncherApp
837 // after that point
Adam Cohendcd297f2013-06-18 13:13:40 -0700838 public long generateNewItemId() {
839 if (mMaxItemId < 0) {
840 throw new RuntimeException("Error: max item id was not initialized");
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700841 }
Adam Cohendcd297f2013-06-18 13:13:40 -0700842 mMaxItemId += 1;
843 return mMaxItemId;
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700844 }
845
Winson Chungc763c4e2013-07-19 13:49:06 -0700846 public void updateMaxItemId(long id) {
847 mMaxItemId = id + 1;
848 }
849
Adam Cohendcd297f2013-06-18 13:13:40 -0700850 private long initializeMaxItemId(SQLiteDatabase db) {
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700851 Cursor c = db.rawQuery("SELECT MAX(_id) FROM favorites", null);
852
853 // get the result
854 final int maxIdIndex = 0;
855 long id = -1;
856 if (c != null && c.moveToNext()) {
857 id = c.getLong(maxIdIndex);
858 }
Michael Jurka5130e402011-10-13 04:55:35 -0700859 if (c != null) {
860 c.close();
861 }
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700862
863 if (id == -1) {
Adam Cohendcd297f2013-06-18 13:13:40 -0700864 throw new RuntimeException("Error: could not query max item id");
865 }
866
867 return id;
868 }
869
870 // Generates a new ID to use for an workspace screen in your database. This method
871 // should be only called from the main UI thread. As an exception, we do call it when we
872 // call the constructor from the worker thread; however, this doesn't extend until after the
873 // constructor is called, and we only pass a reference to LauncherProvider to LauncherApp
874 // after that point
875 public long generateNewScreenId() {
876 if (mMaxScreenId < 0) {
877 throw new RuntimeException("Error: max screen id was not initialized");
878 }
879 mMaxScreenId += 1;
Winson Chunga90303b2013-11-15 13:05:06 -0800880 // Log to disk
881 Launcher.addDumpLog(TAG, "11683562 - generateNewScreenId(): " + mMaxScreenId, true);
Adam Cohendcd297f2013-06-18 13:13:40 -0700882 return mMaxScreenId;
883 }
884
885 public void updateMaxScreenId(long maxScreenId) {
Winson Chunga90303b2013-11-15 13:05:06 -0800886 // Log to disk
887 Launcher.addDumpLog(TAG, "11683562 - updateMaxScreenId(): " + maxScreenId, true);
Adam Cohendcd297f2013-06-18 13:13:40 -0700888 mMaxScreenId = maxScreenId;
889 }
890
891 private long initializeMaxScreenId(SQLiteDatabase db) {
892 Cursor c = db.rawQuery("SELECT MAX(" + LauncherSettings.WorkspaceScreens._ID + ") FROM " + TABLE_WORKSPACE_SCREENS, null);
893
894 // get the result
895 final int maxIdIndex = 0;
896 long id = -1;
897 if (c != null && c.moveToNext()) {
898 id = c.getLong(maxIdIndex);
899 }
900 if (c != null) {
901 c.close();
902 }
903
904 if (id == -1) {
905 throw new RuntimeException("Error: could not query max screen id");
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700906 }
907
Winson Chunga90303b2013-11-15 13:05:06 -0800908 // Log to disk
909 Launcher.addDumpLog(TAG, "11683562 - initializeMaxScreenId(): " + id, true);
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700910 return id;
Joe Onorato0589f0f2010-02-08 13:44:00 -0800911 }
912
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800913 /**
The Android Open Source Project7376fae2009-03-11 12:11:58 -0700914 * Upgrade existing clock and photo frame widgets into their new widget
Bjorn Bringert93c45762009-12-16 13:19:47 +0000915 * equivalents.
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800916 */
917 private void convertWidgets(SQLiteDatabase db) {
Bjorn Bringert34251342009-12-15 13:33:11 +0000918 final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800919 final int[] bindSources = new int[] {
920 Favorites.ITEM_TYPE_WIDGET_CLOCK,
921 Favorites.ITEM_TYPE_WIDGET_PHOTO_FRAME,
Bjorn Bringert7984c942009-12-09 15:38:25 +0000922 Favorites.ITEM_TYPE_WIDGET_SEARCH,
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800923 };
Bjorn Bringert7984c942009-12-09 15:38:25 +0000924
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800925 final String selectWhere = buildOrWhereString(Favorites.ITEM_TYPE, bindSources);
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700926
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800927 Cursor c = null;
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700928
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800929 db.beginTransaction();
930 try {
931 // Select and iterate through each matching widget
Bjorn Bringert7984c942009-12-09 15:38:25 +0000932 c = db.query(TABLE_FAVORITES, new String[] { Favorites._ID, Favorites.ITEM_TYPE },
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800933 selectWhere, null, null, null, null);
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700934
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800935 if (LOGD) Log.d(TAG, "found upgrade cursor count=" + c.getCount());
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700936
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800937 final ContentValues values = new ContentValues();
938 while (c != null && c.moveToNext()) {
939 long favoriteId = c.getLong(0);
Bjorn Bringert7984c942009-12-09 15:38:25 +0000940 int favoriteType = c.getInt(1);
941
The Android Open Source Project7376fae2009-03-11 12:11:58 -0700942 // Allocate and update database with new appWidgetId
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800943 try {
The Android Open Source Project7376fae2009-03-11 12:11:58 -0700944 int appWidgetId = mAppWidgetHost.allocateAppWidgetId();
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700945
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800946 if (LOGD) {
947 Log.d(TAG, "allocated appWidgetId=" + appWidgetId
948 + " for favoriteId=" + favoriteId);
949 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800950 values.clear();
Bjorn Bringert7984c942009-12-09 15:38:25 +0000951 values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_APPWIDGET);
952 values.put(Favorites.APPWIDGET_ID, appWidgetId);
953
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800954 // Original widgets might not have valid spans when upgrading
Bjorn Bringert7984c942009-12-09 15:38:25 +0000955 if (favoriteType == Favorites.ITEM_TYPE_WIDGET_SEARCH) {
956 values.put(LauncherSettings.Favorites.SPANX, 4);
957 values.put(LauncherSettings.Favorites.SPANY, 1);
958 } else {
959 values.put(LauncherSettings.Favorites.SPANX, 2);
960 values.put(LauncherSettings.Favorites.SPANY, 2);
961 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800962
963 String updateWhere = Favorites._ID + "=" + favoriteId;
964 db.update(TABLE_FAVORITES, values, updateWhere, null);
Bjorn Bringert34251342009-12-15 13:33:11 +0000965
Bjorn Bringert34251342009-12-15 13:33:11 +0000966 if (favoriteType == Favorites.ITEM_TYPE_WIDGET_CLOCK) {
Michael Jurka8b805b12012-04-18 14:23:14 -0700967 // TODO: check return value
968 appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId,
Bjorn Bringert34251342009-12-15 13:33:11 +0000969 new ComponentName("com.android.alarmclock",
970 "com.android.alarmclock.AnalogAppWidgetProvider"));
971 } else if (favoriteType == Favorites.ITEM_TYPE_WIDGET_PHOTO_FRAME) {
Michael Jurka8b805b12012-04-18 14:23:14 -0700972 // TODO: check return value
973 appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId,
Bjorn Bringert34251342009-12-15 13:33:11 +0000974 new ComponentName("com.android.camera",
975 "com.android.camera.PhotoAppWidgetProvider"));
976 } else if (favoriteType == Favorites.ITEM_TYPE_WIDGET_SEARCH) {
Michael Jurka8b805b12012-04-18 14:23:14 -0700977 // TODO: check return value
978 appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId,
Bjorn Bringertcd8fec02010-01-14 13:26:43 +0000979 getSearchWidgetProvider());
Bjorn Bringert34251342009-12-15 13:33:11 +0000980 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800981 } catch (RuntimeException ex) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800982 Log.e(TAG, "Problem allocating appWidgetId", ex);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800983 }
984 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700985
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800986 db.setTransactionSuccessful();
987 } catch (SQLException ex) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800988 Log.w(TAG, "Problem while allocating appWidgetIds for existing widgets", ex);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800989 } finally {
990 db.endTransaction();
991 if (c != null) {
992 c.close();
993 }
994 }
Winson Chungc763c4e2013-07-19 13:49:06 -0700995
996 // Update max item id
997 mMaxItemId = initializeMaxItemId(db);
998 if (LOGD) Log.d(TAG, "mMaxItemId: " + mMaxItemId);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800999 }
1000
Michael Jurka8b805b12012-04-18 14:23:14 -07001001 private static final void beginDocument(XmlPullParser parser, String firstElementName)
1002 throws XmlPullParserException, IOException {
1003 int type;
Michael Jurka9bc8eba2012-05-21 20:36:44 -07001004 while ((type = parser.next()) != XmlPullParser.START_TAG
1005 && type != XmlPullParser.END_DOCUMENT) {
Michael Jurka8b805b12012-04-18 14:23:14 -07001006 ;
1007 }
1008
Michael Jurka9bc8eba2012-05-21 20:36:44 -07001009 if (type != XmlPullParser.START_TAG) {
Michael Jurka8b805b12012-04-18 14:23:14 -07001010 throw new XmlPullParserException("No start tag found");
1011 }
1012
1013 if (!parser.getName().equals(firstElementName)) {
1014 throw new XmlPullParserException("Unexpected start tag: found " + parser.getName() +
1015 ", expected " + firstElementName);
1016 }
1017 }
1018
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001019 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001020 * Loads the default set of favorite packages from an xml file.
1021 *
1022 * @param db The database to write the values into
Winson Chung3d503fb2011-07-13 17:25:49 -07001023 * @param filterContainerId The specific container id of items to load
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001024 */
Winson Chung6d092682011-11-16 18:43:26 -08001025 private int loadFavorites(SQLiteDatabase db, int workspaceResourceId) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001026 Intent intent = new Intent(Intent.ACTION_MAIN, null);
1027 intent.addCategory(Intent.CATEGORY_LAUNCHER);
1028 ContentValues values = new ContentValues();
1029
Daniel Sandler57dac262013-10-03 13:28:36 -04001030 if (LOGD) Log.v(TAG, String.format("Loading favorites from resid=0x%08x", workspaceResourceId));
1031
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001032 PackageManager packageManager = mContext.getPackageManager();
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001033 int i = 0;
1034 try {
Winson Chung6d092682011-11-16 18:43:26 -08001035 XmlResourceParser parser = mContext.getResources().getXml(workspaceResourceId);
The Android Open Source Projectf96811c2009-03-18 17:39:48 -07001036 AttributeSet attrs = Xml.asAttributeSet(parser);
Michael Jurka8b805b12012-04-18 14:23:14 -07001037 beginDocument(parser, TAG_FAVORITES);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001038
The Android Open Source Projectf96811c2009-03-18 17:39:48 -07001039 final int depth = parser.getDepth();
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001040
The Android Open Source Projectf96811c2009-03-18 17:39:48 -07001041 int type;
1042 while (((type = parser.next()) != XmlPullParser.END_TAG ||
1043 parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
1044
1045 if (type != XmlPullParser.START_TAG) {
1046 continue;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001047 }
1048
The Android Open Source Projectf96811c2009-03-18 17:39:48 -07001049 boolean added = false;
1050 final String name = parser.getName();
1051
Daniel Sandler57dac262013-10-03 13:28:36 -04001052 if (TAG_INCLUDE.equals(name)) {
1053 final TypedArray a = mContext.obtainStyledAttributes(attrs, R.styleable.Include);
1054
1055 final int resId = a.getResourceId(R.styleable.Include_workspace, 0);
1056
1057 if (LOGD) Log.v(TAG, String.format(("%" + (2*(depth+1)) + "s<include workspace=%08x>"),
1058 "", resId));
1059
1060 if (resId != 0 && resId != workspaceResourceId) {
1061 // recursively load some more favorites, why not?
1062 i += loadFavorites(db, resId);
1063 added = false;
Daniel Sandler57dac262013-10-03 13:28:36 -04001064 } else {
1065 Log.w(TAG, String.format("Skipping <include workspace=0x%08x>", resId));
1066 }
1067
1068 a.recycle();
1069
1070 if (LOGD) Log.v(TAG, String.format(("%" + (2*(depth+1)) + "s</include>"), ""));
1071 continue;
1072 }
1073
1074 // Assuming it's a <favorite> at this point
The Android Open Source Projectf96811c2009-03-18 17:39:48 -07001075 TypedArray a = mContext.obtainStyledAttributes(attrs, R.styleable.Favorite);
1076
Winson Chung3d503fb2011-07-13 17:25:49 -07001077 long container = LauncherSettings.Favorites.CONTAINER_DESKTOP;
1078 if (a.hasValue(R.styleable.Favorite_container)) {
1079 container = Long.valueOf(a.getString(R.styleable.Favorite_container));
1080 }
The Android Open Source Projectf96811c2009-03-18 17:39:48 -07001081
Winson Chung6d092682011-11-16 18:43:26 -08001082 String screen = a.getString(R.styleable.Favorite_screen);
1083 String x = a.getString(R.styleable.Favorite_x);
1084 String y = a.getString(R.styleable.Favorite_y);
1085
Winson Chung6d092682011-11-16 18:43:26 -08001086 values.clear();
1087 values.put(LauncherSettings.Favorites.CONTAINER, container);
1088 values.put(LauncherSettings.Favorites.SCREEN, screen);
1089 values.put(LauncherSettings.Favorites.CELLX, x);
1090 values.put(LauncherSettings.Favorites.CELLY, y);
1091
Daniel Sandler57dac262013-10-03 13:28:36 -04001092 if (LOGD) {
1093 final String title = a.getString(R.styleable.Favorite_title);
1094 final String pkg = a.getString(R.styleable.Favorite_packageName);
1095 final String something = title != null ? title : pkg;
1096 Log.v(TAG, String.format(
1097 ("%" + (2*(depth+1)) + "s<%s%s c=%d s=%s x=%s y=%s>"),
1098 "", name,
1099 (something == null ? "" : (" \"" + something + "\"")),
1100 container, screen, x, y));
1101 }
1102
Winson Chung6d092682011-11-16 18:43:26 -08001103 if (TAG_FAVORITE.equals(name)) {
1104 long id = addAppShortcut(db, values, a, packageManager, intent);
1105 added = id >= 0;
1106 } else if (TAG_SEARCH.equals(name)) {
1107 added = addSearchWidget(db, values);
1108 } else if (TAG_CLOCK.equals(name)) {
1109 added = addClockWidget(db, values);
1110 } else if (TAG_APPWIDGET.equals(name)) {
Winson Chungb3302ae2012-05-01 10:19:14 -07001111 added = addAppWidget(parser, attrs, type, db, values, a, packageManager);
Winson Chung6d092682011-11-16 18:43:26 -08001112 } else if (TAG_SHORTCUT.equals(name)) {
1113 long id = addUriShortcut(db, values, a);
1114 added = id >= 0;
1115 } else if (TAG_FOLDER.equals(name)) {
1116 String title;
1117 int titleResId = a.getResourceId(R.styleable.Favorite_title, -1);
1118 if (titleResId != -1) {
1119 title = mContext.getResources().getString(titleResId);
1120 } else {
1121 title = mContext.getResources().getString(R.string.folder_name);
Winson Chung3d503fb2011-07-13 17:25:49 -07001122 }
Winson Chung6d092682011-11-16 18:43:26 -08001123 values.put(LauncherSettings.Favorites.TITLE, title);
1124 long folderId = addFolder(db, values);
1125 added = folderId >= 0;
Winson Chung3d503fb2011-07-13 17:25:49 -07001126
Winson Chung6d092682011-11-16 18:43:26 -08001127 ArrayList<Long> folderItems = new ArrayList<Long>();
Winson Chung3d503fb2011-07-13 17:25:49 -07001128
Winson Chung6d092682011-11-16 18:43:26 -08001129 int folderDepth = parser.getDepth();
1130 while ((type = parser.next()) != XmlPullParser.END_TAG ||
1131 parser.getDepth() > folderDepth) {
1132 if (type != XmlPullParser.START_TAG) {
1133 continue;
1134 }
1135 final String folder_item_name = parser.getName();
1136
1137 TypedArray ar = mContext.obtainStyledAttributes(attrs,
1138 R.styleable.Favorite);
1139 values.clear();
1140 values.put(LauncherSettings.Favorites.CONTAINER, folderId);
1141
Daniel Sandler57dac262013-10-03 13:28:36 -04001142 if (LOGD) {
1143 final String pkg = ar.getString(R.styleable.Favorite_packageName);
1144 final String uri = ar.getString(R.styleable.Favorite_uri);
1145 Log.v(TAG, String.format(("%" + (2*(folderDepth+1)) + "s<%s \"%s\">"), "",
1146 folder_item_name, uri != null ? uri : pkg));
1147 }
1148
Winson Chung6d092682011-11-16 18:43:26 -08001149 if (TAG_FAVORITE.equals(folder_item_name) && folderId >= 0) {
1150 long id =
1151 addAppShortcut(db, values, ar, packageManager, intent);
1152 if (id >= 0) {
1153 folderItems.add(id);
1154 }
1155 } else if (TAG_SHORTCUT.equals(folder_item_name) && folderId >= 0) {
1156 long id = addUriShortcut(db, values, ar);
1157 if (id >= 0) {
1158 folderItems.add(id);
1159 }
Adam Cohen228da5a2011-07-27 22:23:47 -07001160 } else {
Winson Chung6d092682011-11-16 18:43:26 -08001161 throw new RuntimeException("Folders can " +
1162 "contain only shortcuts");
Adam Cohen228da5a2011-07-27 22:23:47 -07001163 }
Winson Chung6d092682011-11-16 18:43:26 -08001164 ar.recycle();
1165 }
1166 // We can only have folders with >= 2 items, so we need to remove the
1167 // folder and clean up if less than 2 items were included, or some
1168 // failed to add, and less than 2 were actually added
1169 if (folderItems.size() < 2 && folderId >= 0) {
1170 // We just delete the folder and any items that made it
1171 deleteId(db, folderId);
1172 if (folderItems.size() > 0) {
1173 deleteId(db, folderItems.get(0));
Adam Cohen228da5a2011-07-27 22:23:47 -07001174 }
Winson Chung6d092682011-11-16 18:43:26 -08001175 added = false;
Winson Chung3d503fb2011-07-13 17:25:49 -07001176 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001177 }
The Android Open Source Projectf96811c2009-03-18 17:39:48 -07001178 if (added) i++;
The Android Open Source Projectf96811c2009-03-18 17:39:48 -07001179 a.recycle();
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001180 }
1181 } catch (XmlPullParserException e) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -08001182 Log.w(TAG, "Got exception parsing favorites.", e);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001183 } catch (IOException e) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -08001184 Log.w(TAG, "Got exception parsing favorites.", e);
Winson Chung3d503fb2011-07-13 17:25:49 -07001185 } catch (RuntimeException e) {
1186 Log.w(TAG, "Got exception parsing favorites.", e);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001187 }
The Android Open Source Projectf96811c2009-03-18 17:39:48 -07001188
Winson Chungc763c4e2013-07-19 13:49:06 -07001189 // Update the max item id after we have loaded the database
1190 if (mMaxItemId == -1) {
1191 mMaxItemId = initializeMaxItemId(db);
1192 }
1193
The Android Open Source Projectf96811c2009-03-18 17:39:48 -07001194 return i;
1195 }
1196
Adam Cohen228da5a2011-07-27 22:23:47 -07001197 private long addAppShortcut(SQLiteDatabase db, ContentValues values, TypedArray a,
The Android Open Source Projectf96811c2009-03-18 17:39:48 -07001198 PackageManager packageManager, Intent intent) {
Adam Cohen228da5a2011-07-27 22:23:47 -07001199 long id = -1;
The Android Open Source Projectf96811c2009-03-18 17:39:48 -07001200 ActivityInfo info;
1201 String packageName = a.getString(R.styleable.Favorite_packageName);
1202 String className = a.getString(R.styleable.Favorite_className);
1203 try {
Romain Guy693599f2010-03-23 10:58:18 -07001204 ComponentName cn;
1205 try {
1206 cn = new ComponentName(packageName, className);
1207 info = packageManager.getActivityInfo(cn, 0);
1208 } catch (PackageManager.NameNotFoundException nnfe) {
1209 String[] packages = packageManager.currentToCanonicalPackageNames(
1210 new String[] { packageName });
1211 cn = new ComponentName(packages[0], className);
1212 info = packageManager.getActivityInfo(cn, 0);
1213 }
Adam Cohendcd297f2013-06-18 13:13:40 -07001214 id = generateNewItemId();
The Android Open Source Projectf96811c2009-03-18 17:39:48 -07001215 intent.setComponent(cn);
Romain Guy693599f2010-03-23 10:58:18 -07001216 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
1217 Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
Romain Guy1ce1a242009-06-23 17:34:54 -07001218 values.put(Favorites.INTENT, intent.toUri(0));
The Android Open Source Projectf96811c2009-03-18 17:39:48 -07001219 values.put(Favorites.TITLE, info.loadLabel(packageManager).toString());
1220 values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_APPLICATION);
1221 values.put(Favorites.SPANX, 1);
1222 values.put(Favorites.SPANY, 1);
Adam Cohendcd297f2013-06-18 13:13:40 -07001223 values.put(Favorites._ID, generateNewItemId());
Adam Cohen228da5a2011-07-27 22:23:47 -07001224 if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values) < 0) {
1225 return -1;
1226 }
The Android Open Source Projectf96811c2009-03-18 17:39:48 -07001227 } catch (PackageManager.NameNotFoundException e) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -08001228 Log.w(TAG, "Unable to add favorite: " + packageName +
The Android Open Source Projectf96811c2009-03-18 17:39:48 -07001229 "/" + className, e);
The Android Open Source Projectf96811c2009-03-18 17:39:48 -07001230 }
Adam Cohen228da5a2011-07-27 22:23:47 -07001231 return id;
1232 }
1233
1234 private long addFolder(SQLiteDatabase db, ContentValues values) {
1235 values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_FOLDER);
1236 values.put(Favorites.SPANX, 1);
1237 values.put(Favorites.SPANY, 1);
Adam Cohendcd297f2013-06-18 13:13:40 -07001238 long id = generateNewItemId();
Adam Cohen228da5a2011-07-27 22:23:47 -07001239 values.put(Favorites._ID, id);
1240 if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values) <= 0) {
1241 return -1;
1242 } else {
1243 return id;
1244 }
The Android Open Source Projectf96811c2009-03-18 17:39:48 -07001245 }
1246
Bjorn Bringertcd8fec02010-01-14 13:26:43 +00001247 private ComponentName getSearchWidgetProvider() {
1248 SearchManager searchManager =
1249 (SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE);
1250 ComponentName searchComponent = searchManager.getGlobalSearchActivity();
1251 if (searchComponent == null) return null;
1252 return getProviderInPackage(searchComponent.getPackageName());
1253 }
1254
1255 /**
1256 * Gets an appwidget provider from the given package. If the package contains more than
1257 * one appwidget provider, an arbitrary one is returned.
1258 */
1259 private ComponentName getProviderInPackage(String packageName) {
1260 AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext);
1261 List<AppWidgetProviderInfo> providers = appWidgetManager.getInstalledProviders();
1262 if (providers == null) return null;
1263 final int providerCount = providers.size();
1264 for (int i = 0; i < providerCount; i++) {
1265 ComponentName provider = providers.get(i).provider;
1266 if (provider != null && provider.getPackageName().equals(packageName)) {
1267 return provider;
1268 }
1269 }
1270 return null;
1271 }
1272
The Android Open Source Projectf96811c2009-03-18 17:39:48 -07001273 private boolean addSearchWidget(SQLiteDatabase db, ContentValues values) {
Bjorn Bringertcd8fec02010-01-14 13:26:43 +00001274 ComponentName cn = getSearchWidgetProvider();
Winson Chungb3302ae2012-05-01 10:19:14 -07001275 return addAppWidget(db, values, cn, 4, 1, null);
The Android Open Source Projectf96811c2009-03-18 17:39:48 -07001276 }
1277
1278 private boolean addClockWidget(SQLiteDatabase db, ContentValues values) {
Bjorn Bringert34251342009-12-15 13:33:11 +00001279 ComponentName cn = new ComponentName("com.android.alarmclock",
1280 "com.android.alarmclock.AnalogAppWidgetProvider");
Winson Chungb3302ae2012-05-01 10:19:14 -07001281 return addAppWidget(db, values, cn, 2, 2, null);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001282 }
Adam Cohen228da5a2011-07-27 22:23:47 -07001283
Winson Chungb3302ae2012-05-01 10:19:14 -07001284 private boolean addAppWidget(XmlResourceParser parser, AttributeSet attrs, int type,
1285 SQLiteDatabase db, ContentValues values, TypedArray a,
1286 PackageManager packageManager) throws XmlPullParserException, IOException {
Romain Guy693599f2010-03-23 10:58:18 -07001287
Mike Cleronb87bd162009-10-30 16:36:56 -07001288 String packageName = a.getString(R.styleable.Favorite_packageName);
1289 String className = a.getString(R.styleable.Favorite_className);
1290
1291 if (packageName == null || className == null) {
1292 return false;
1293 }
Romain Guy693599f2010-03-23 10:58:18 -07001294
1295 boolean hasPackage = true;
Mike Cleronb87bd162009-10-30 16:36:56 -07001296 ComponentName cn = new ComponentName(packageName, className);
Romain Guy693599f2010-03-23 10:58:18 -07001297 try {
1298 packageManager.getReceiverInfo(cn, 0);
1299 } catch (Exception e) {
1300 String[] packages = packageManager.currentToCanonicalPackageNames(
1301 new String[] { packageName });
1302 cn = new ComponentName(packages[0], className);
1303 try {
1304 packageManager.getReceiverInfo(cn, 0);
1305 } catch (Exception e1) {
1306 hasPackage = false;
1307 }
1308 }
1309
1310 if (hasPackage) {
1311 int spanX = a.getInt(R.styleable.Favorite_spanX, 0);
1312 int spanY = a.getInt(R.styleable.Favorite_spanY, 0);
Winson Chungb3302ae2012-05-01 10:19:14 -07001313
1314 // Read the extras
1315 Bundle extras = new Bundle();
1316 int widgetDepth = parser.getDepth();
1317 while ((type = parser.next()) != XmlPullParser.END_TAG ||
1318 parser.getDepth() > widgetDepth) {
1319 if (type != XmlPullParser.START_TAG) {
1320 continue;
1321 }
1322
1323 TypedArray ar = mContext.obtainStyledAttributes(attrs, R.styleable.Extra);
1324 if (TAG_EXTRA.equals(parser.getName())) {
1325 String key = ar.getString(R.styleable.Extra_key);
1326 String value = ar.getString(R.styleable.Extra_value);
1327 if (key != null && value != null) {
1328 extras.putString(key, value);
1329 } else {
1330 throw new RuntimeException("Widget extras must have a key and value");
1331 }
1332 } else {
1333 throw new RuntimeException("Widgets can contain only extras");
1334 }
1335 ar.recycle();
1336 }
1337
1338 return addAppWidget(db, values, cn, spanX, spanY, extras);
Romain Guy693599f2010-03-23 10:58:18 -07001339 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -07001340
Romain Guy693599f2010-03-23 10:58:18 -07001341 return false;
Bjorn Bringert7984c942009-12-09 15:38:25 +00001342 }
Bjorn Bringert7984c942009-12-09 15:38:25 +00001343 private boolean addAppWidget(SQLiteDatabase db, ContentValues values, ComponentName cn,
Winson Chungb3302ae2012-05-01 10:19:14 -07001344 int spanX, int spanY, Bundle extras) {
Mike Cleronb87bd162009-10-30 16:36:56 -07001345 boolean allocatedAppWidgets = false;
1346 final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext);
1347
1348 try {
1349 int appWidgetId = mAppWidgetHost.allocateAppWidgetId();
Daniel Lehmannc3a80402012-04-23 21:35:11 -07001350
Mike Cleronb87bd162009-10-30 16:36:56 -07001351 values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_APPWIDGET);
Bjorn Bringert7984c942009-12-09 15:38:25 +00001352 values.put(Favorites.SPANX, spanX);
1353 values.put(Favorites.SPANY, spanY);
Mike Cleronb87bd162009-10-30 16:36:56 -07001354 values.put(Favorites.APPWIDGET_ID, appWidgetId);
Chris Wrend5e66bf2013-09-16 14:02:29 -04001355 values.put(Favorites.APPWIDGET_PROVIDER, cn.flattenToString());
Adam Cohendcd297f2013-06-18 13:13:40 -07001356 values.put(Favorites._ID, generateNewItemId());
Michael Jurkaa8c760d2011-04-28 14:59:33 -07001357 dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values);
Mike Cleronb87bd162009-10-30 16:36:56 -07001358
1359 allocatedAppWidgets = true;
Daniel Lehmannc3a80402012-04-23 21:35:11 -07001360
Michael Jurka8b805b12012-04-18 14:23:14 -07001361 // TODO: need to check return value
1362 appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, cn);
Winson Chungb3302ae2012-05-01 10:19:14 -07001363
1364 // Send a broadcast to configure the widget
1365 if (extras != null && !extras.isEmpty()) {
1366 Intent intent = new Intent(ACTION_APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE);
1367 intent.setComponent(cn);
1368 intent.putExtras(extras);
1369 intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
1370 mContext.sendBroadcast(intent);
1371 }
Mike Cleronb87bd162009-10-30 16:36:56 -07001372 } catch (RuntimeException ex) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -08001373 Log.e(TAG, "Problem allocating appWidgetId", ex);
Mike Cleronb87bd162009-10-30 16:36:56 -07001374 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -07001375
Mike Cleronb87bd162009-10-30 16:36:56 -07001376 return allocatedAppWidgets;
1377 }
Adam Cohen228da5a2011-07-27 22:23:47 -07001378
1379 private long addUriShortcut(SQLiteDatabase db, ContentValues values,
Mike Cleronb87bd162009-10-30 16:36:56 -07001380 TypedArray a) {
1381 Resources r = mContext.getResources();
1382
1383 final int iconResId = a.getResourceId(R.styleable.Favorite_icon, 0);
1384 final int titleResId = a.getResourceId(R.styleable.Favorite_title, 0);
1385
Romain Guy7eb9e5e2009-12-02 20:10:07 -08001386 Intent intent;
Mike Cleronb87bd162009-10-30 16:36:56 -07001387 String uri = null;
1388 try {
1389 uri = a.getString(R.styleable.Favorite_uri);
1390 intent = Intent.parseUri(uri, 0);
1391 } catch (URISyntaxException e) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -08001392 Log.w(TAG, "Shortcut has malformed uri: " + uri);
Adam Cohen228da5a2011-07-27 22:23:47 -07001393 return -1; // Oh well
Mike Cleronb87bd162009-10-30 16:36:56 -07001394 }
1395
1396 if (iconResId == 0 || titleResId == 0) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -08001397 Log.w(TAG, "Shortcut is missing title or icon resource ID");
Adam Cohen228da5a2011-07-27 22:23:47 -07001398 return -1;
Mike Cleronb87bd162009-10-30 16:36:56 -07001399 }
1400
Adam Cohendcd297f2013-06-18 13:13:40 -07001401 long id = generateNewItemId();
Mike Cleronb87bd162009-10-30 16:36:56 -07001402 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1403 values.put(Favorites.INTENT, intent.toUri(0));
1404 values.put(Favorites.TITLE, r.getString(titleResId));
1405 values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_SHORTCUT);
1406 values.put(Favorites.SPANX, 1);
1407 values.put(Favorites.SPANY, 1);
1408 values.put(Favorites.ICON_TYPE, Favorites.ICON_TYPE_RESOURCE);
1409 values.put(Favorites.ICON_PACKAGE, mContext.getPackageName());
1410 values.put(Favorites.ICON_RESOURCE, r.getResourceName(iconResId));
Adam Cohen228da5a2011-07-27 22:23:47 -07001411 values.put(Favorites._ID, id);
Mike Cleronb87bd162009-10-30 16:36:56 -07001412
Adam Cohen228da5a2011-07-27 22:23:47 -07001413 if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values) < 0) {
1414 return -1;
1415 }
1416 return id;
Mike Cleronb87bd162009-10-30 16:36:56 -07001417 }
1418 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -07001419
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001420 /**
1421 * Build a query string that will match any row where the column matches
1422 * anything in the values list.
1423 */
1424 static String buildOrWhereString(String column, int[] values) {
1425 StringBuilder selectWhere = new StringBuilder();
1426 for (int i = values.length - 1; i >= 0; i--) {
1427 selectWhere.append(column).append("=").append(values[i]);
1428 if (i > 0) {
1429 selectWhere.append(" OR ");
1430 }
1431 }
1432 return selectWhere.toString();
1433 }
1434
1435 static class SqlArguments {
1436 public final String table;
1437 public final String where;
1438 public final String[] args;
1439
1440 SqlArguments(Uri url, String where, String[] args) {
1441 if (url.getPathSegments().size() == 1) {
1442 this.table = url.getPathSegments().get(0);
1443 this.where = where;
1444 this.args = args;
1445 } else if (url.getPathSegments().size() != 2) {
1446 throw new IllegalArgumentException("Invalid URI: " + url);
1447 } else if (!TextUtils.isEmpty(where)) {
1448 throw new UnsupportedOperationException("WHERE clause not supported: " + url);
1449 } else {
1450 this.table = url.getPathSegments().get(0);
Daniel Lehmannc3a80402012-04-23 21:35:11 -07001451 this.where = "_id=" + ContentUris.parseId(url);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001452 this.args = null;
1453 }
1454 }
1455
1456 SqlArguments(Uri url) {
1457 if (url.getPathSegments().size() == 1) {
1458 table = url.getPathSegments().get(0);
1459 where = null;
1460 args = null;
1461 } else {
1462 throw new IllegalArgumentException("Invalid URI: " + url);
1463 }
1464 }
1465 }
1466}