blob: 54de63531fd7ba4b299a3bfe18f309d58204e924 [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?
Dan Sandlera6a296b2013-11-26 12:48:01 -050075 static final boolean IMPORT_LAUNCHER2_DATABASE = false;
Dan Sandlerf0b8dac2013-11-19 12:21:25 -050076
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
Adam Cohen6dbe0492013-12-02 17:00:14 -0800293 private static boolean shouldImportLauncher2Database(Context context) {
294 boolean isTablet = context.getResources().getBoolean(R.bool.is_tablet);
295
296 // We don't import the old databse for tablets, as the grid size has changed.
297 return !isTablet && IMPORT_LAUNCHER2_DATABASE;
298 }
299
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800300 private static class DatabaseHelper extends SQLiteOpenHelper {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800301 private static final String TAG_FAVORITES = "favorites";
302 private static final String TAG_FAVORITE = "favorite";
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700303 private static final String TAG_CLOCK = "clock";
304 private static final String TAG_SEARCH = "search";
Mike Cleronb87bd162009-10-30 16:36:56 -0700305 private static final String TAG_APPWIDGET = "appwidget";
306 private static final String TAG_SHORTCUT = "shortcut";
Adam Cohen228da5a2011-07-27 22:23:47 -0700307 private static final String TAG_FOLDER = "folder";
Winson Chungb3302ae2012-05-01 10:19:14 -0700308 private static final String TAG_EXTRA = "extra";
Daniel Sandler57dac262013-10-03 13:28:36 -0400309 private static final String TAG_INCLUDE = "include";
Winson Chung3d503fb2011-07-13 17:25:49 -0700310
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800311 private final Context mContext;
The Android Open Source Project7376fae2009-03-11 12:11:58 -0700312 private final AppWidgetHost mAppWidgetHost;
Adam Cohendcd297f2013-06-18 13:13:40 -0700313 private long mMaxItemId = -1;
314 private long mMaxScreenId = -1;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800315
316 DatabaseHelper(Context context) {
317 super(context, DATABASE_NAME, null, DATABASE_VERSION);
318 mContext = context;
The Android Open Source Project7376fae2009-03-11 12:11:58 -0700319 mAppWidgetHost = new AppWidgetHost(context, Launcher.APPWIDGET_HOST_ID);
Winson Chung3d503fb2011-07-13 17:25:49 -0700320
321 // In the case where neither onCreate nor onUpgrade gets called, we read the maxId from
322 // the DB here
Adam Cohendcd297f2013-06-18 13:13:40 -0700323 if (mMaxItemId == -1) {
324 mMaxItemId = initializeMaxItemId(getWritableDatabase());
325 }
326 if (mMaxScreenId == -1) {
327 mMaxScreenId = initializeMaxScreenId(getWritableDatabase());
Winson Chung3d503fb2011-07-13 17:25:49 -0700328 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800329 }
330
Jeffrey Sharkey2bbcae12009-03-31 14:37:57 -0700331 /**
332 * Send notification that we've deleted the {@link AppWidgetHost},
333 * probably as part of the initial database creation. The receiver may
334 * want to re-call {@link AppWidgetHost#startListening()} to ensure
335 * callbacks are correctly set.
336 */
337 private void sendAppWidgetResetNotify() {
338 final ContentResolver resolver = mContext.getContentResolver();
339 resolver.notifyChange(CONTENT_APPWIDGET_RESET_URI, null);
340 }
341
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800342 @Override
343 public void onCreate(SQLiteDatabase db) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800344 if (LOGD) Log.d(TAG, "creating new launcher database");
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700345
Adam Cohendcd297f2013-06-18 13:13:40 -0700346 mMaxItemId = 1;
347 mMaxScreenId = 0;
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700348
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800349 db.execSQL("CREATE TABLE favorites (" +
350 "_id INTEGER PRIMARY KEY," +
351 "title TEXT," +
352 "intent TEXT," +
353 "container INTEGER," +
354 "screen INTEGER," +
355 "cellX INTEGER," +
356 "cellY INTEGER," +
357 "spanX INTEGER," +
358 "spanY INTEGER," +
359 "itemType INTEGER," +
The Android Open Source Projectca9475f2009-03-13 13:04:24 -0700360 "appWidgetId INTEGER NOT NULL DEFAULT -1," +
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800361 "isShortcut INTEGER," +
362 "iconType INTEGER," +
363 "iconPackage TEXT," +
364 "iconResource TEXT," +
365 "icon BLOB," +
366 "uri TEXT," +
Chris Wrend5e66bf2013-09-16 14:02:29 -0400367 "displayMode INTEGER," +
Chris Wren1ada10d2013-09-13 18:01:38 -0400368 "appWidgetProvider TEXT," +
369 "modified INTEGER NOT NULL DEFAULT 0" +
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800370 ");");
Adam Cohendcd297f2013-06-18 13:13:40 -0700371 addWorkspacesTable(db);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800372
The Android Open Source Project7376fae2009-03-11 12:11:58 -0700373 // Database was just created, so wipe any previous widgets
374 if (mAppWidgetHost != null) {
375 mAppWidgetHost.deleteHost();
Jeffrey Sharkey2bbcae12009-03-31 14:37:57 -0700376 sendAppWidgetResetNotify();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800377 }
Winson Chung3d503fb2011-07-13 17:25:49 -0700378
Adam Cohen6dbe0492013-12-02 17:00:14 -0800379 if (shouldImportLauncher2Database(mContext)) {
Dan Sandlerf0b8dac2013-11-19 12:21:25 -0500380 // Try converting the old database
381 ContentValuesCallback permuteScreensCb = new ContentValuesCallback() {
382 public void onRow(ContentValues values) {
383 int container = values.getAsInteger(LauncherSettings.Favorites.CONTAINER);
384 if (container == Favorites.CONTAINER_DESKTOP) {
385 int screen = values.getAsInteger(LauncherSettings.Favorites.SCREEN);
386 screen = (int) upgradeLauncherDb_permuteScreens(screen);
387 values.put(LauncherSettings.Favorites.SCREEN, screen);
388 }
389 }
390 };
391 Uri uri = Uri.parse("content://" + Settings.AUTHORITY +
392 "/old_favorites?notify=true");
393 if (!convertDatabase(db, uri, permuteScreensCb, true)) {
394 // Try and upgrade from the Launcher2 db
395 uri = LauncherSettings.Favorites.OLD_CONTENT_URI;
396 if (!convertDatabase(db, uri, permuteScreensCb, false)) {
397 // If we fail, then set a flag to load the default workspace
398 setFlagEmptyDbCreated();
399 return;
Winson Chungc763c4e2013-07-19 13:49:06 -0700400 }
401 }
Dan Sandlerf0b8dac2013-11-19 12:21:25 -0500402 // Right now, in non-default workspace cases, we want to run the final
403 // upgrade code (ie. to fix workspace screen indices -> ids, etc.), so
404 // set that flag too.
405 setFlagJustLoadedOldDb();
406 } else {
407 // Fresh and clean launcher DB.
408 mMaxItemId = initializeMaxItemId(db);
409 setFlagEmptyDbCreated();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800410 }
411 }
412
Adam Cohendcd297f2013-06-18 13:13:40 -0700413 private void addWorkspacesTable(SQLiteDatabase db) {
414 db.execSQL("CREATE TABLE " + TABLE_WORKSPACE_SCREENS + " (" +
415 LauncherSettings.WorkspaceScreens._ID + " INTEGER," +
Chris Wren1ada10d2013-09-13 18:01:38 -0400416 LauncherSettings.WorkspaceScreens.SCREEN_RANK + " INTEGER," +
417 LauncherSettings.ChangeLogColumns.MODIFIED + " INTEGER NOT NULL DEFAULT 0" +
Adam Cohendcd297f2013-06-18 13:13:40 -0700418 ");");
419 }
420
Winson Chungc763c4e2013-07-19 13:49:06 -0700421 private void setFlagJustLoadedOldDb() {
Daniel Sandlercc8befa2013-06-11 14:45:48 -0400422 String spKey = LauncherAppState.getSharedPreferencesKey();
Michael Jurkab85f8a42012-04-25 15:48:32 -0700423 SharedPreferences sp = mContext.getSharedPreferences(spKey, Context.MODE_PRIVATE);
424 SharedPreferences.Editor editor = sp.edit();
Winson Chungc763c4e2013-07-19 13:49:06 -0700425 editor.putBoolean(UPGRADED_FROM_OLD_DATABASE, true);
426 editor.putBoolean(EMPTY_DATABASE_CREATED, false);
Michael Jurkab85f8a42012-04-25 15:48:32 -0700427 editor.commit();
428 }
429
Winson Chungc763c4e2013-07-19 13:49:06 -0700430 private void setFlagEmptyDbCreated() {
431 String spKey = LauncherAppState.getSharedPreferencesKey();
432 SharedPreferences sp = mContext.getSharedPreferences(spKey, Context.MODE_PRIVATE);
433 SharedPreferences.Editor editor = sp.edit();
434 editor.putBoolean(EMPTY_DATABASE_CREATED, true);
435 editor.putBoolean(UPGRADED_FROM_OLD_DATABASE, false);
436 editor.commit();
437 }
438
439 // We rearrange the screens from the old launcher
440 // 12345 -> 34512
441 private long upgradeLauncherDb_permuteScreens(long screen) {
442 if (screen >= 2) {
443 return screen - 2;
444 } else {
445 return screen + 3;
446 }
447 }
448
449 private boolean convertDatabase(SQLiteDatabase db, Uri uri,
450 ContentValuesCallback cb, boolean deleteRows) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800451 if (LOGD) Log.d(TAG, "converting database from an older format, but not onUpgrade");
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800452 boolean converted = false;
453
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800454 final ContentResolver resolver = mContext.getContentResolver();
455 Cursor cursor = null;
456
457 try {
458 cursor = resolver.query(uri, null, null, null, null);
459 } catch (Exception e) {
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700460 // Ignore
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800461 }
462
463 // We already have a favorites database in the old provider
Winson Chungc763c4e2013-07-19 13:49:06 -0700464 if (cursor != null) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800465 try {
Winson Chungc763c4e2013-07-19 13:49:06 -0700466 if (cursor.getCount() > 0) {
467 converted = copyFromCursor(db, cursor, cb) > 0;
468 if (converted && deleteRows) {
469 resolver.delete(uri, null, null);
470 }
471 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800472 } finally {
473 cursor.close();
474 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800475 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700476
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800477 if (converted) {
The Android Open Source Project7376fae2009-03-11 12:11:58 -0700478 // Convert widgets from this import into widgets
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800479 if (LOGD) Log.d(TAG, "converted and now triggering widget upgrade");
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800480 convertWidgets(db);
Winson Chungc763c4e2013-07-19 13:49:06 -0700481
482 // Update max item id
483 mMaxItemId = initializeMaxItemId(db);
484 if (LOGD) Log.d(TAG, "mMaxItemId: " + mMaxItemId);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800485 }
486
487 return converted;
488 }
489
Winson Chungc763c4e2013-07-19 13:49:06 -0700490 private int copyFromCursor(SQLiteDatabase db, Cursor c, ContentValuesCallback cb) {
Romain Guy73b979d2009-06-09 12:57:21 -0700491 final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800492 final int intentIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.INTENT);
493 final int titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE);
494 final int iconTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_TYPE);
495 final int iconIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON);
496 final int iconPackageIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_PACKAGE);
497 final int iconResourceIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_RESOURCE);
498 final int containerIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER);
499 final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE);
500 final int screenIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN);
501 final int cellXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX);
502 final int cellYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY);
503 final int uriIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.URI);
504 final int displayModeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.DISPLAY_MODE);
505
506 ContentValues[] rows = new ContentValues[c.getCount()];
507 int i = 0;
508 while (c.moveToNext()) {
509 ContentValues values = new ContentValues(c.getColumnCount());
Romain Guy73b979d2009-06-09 12:57:21 -0700510 values.put(LauncherSettings.Favorites._ID, c.getLong(idIndex));
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800511 values.put(LauncherSettings.Favorites.INTENT, c.getString(intentIndex));
512 values.put(LauncherSettings.Favorites.TITLE, c.getString(titleIndex));
513 values.put(LauncherSettings.Favorites.ICON_TYPE, c.getInt(iconTypeIndex));
514 values.put(LauncherSettings.Favorites.ICON, c.getBlob(iconIndex));
515 values.put(LauncherSettings.Favorites.ICON_PACKAGE, c.getString(iconPackageIndex));
516 values.put(LauncherSettings.Favorites.ICON_RESOURCE, c.getString(iconResourceIndex));
517 values.put(LauncherSettings.Favorites.CONTAINER, c.getInt(containerIndex));
518 values.put(LauncherSettings.Favorites.ITEM_TYPE, c.getInt(itemTypeIndex));
The Android Open Source Project7376fae2009-03-11 12:11:58 -0700519 values.put(LauncherSettings.Favorites.APPWIDGET_ID, -1);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800520 values.put(LauncherSettings.Favorites.SCREEN, c.getInt(screenIndex));
521 values.put(LauncherSettings.Favorites.CELLX, c.getInt(cellXIndex));
522 values.put(LauncherSettings.Favorites.CELLY, c.getInt(cellYIndex));
523 values.put(LauncherSettings.Favorites.URI, c.getString(uriIndex));
524 values.put(LauncherSettings.Favorites.DISPLAY_MODE, c.getInt(displayModeIndex));
Winson Chungc763c4e2013-07-19 13:49:06 -0700525 if (cb != null) {
526 cb.onRow(values);
527 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800528 rows[i++] = values;
529 }
530
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800531 int total = 0;
Winson Chungc763c4e2013-07-19 13:49:06 -0700532 if (i > 0) {
533 db.beginTransaction();
534 try {
535 int numValues = rows.length;
536 for (i = 0; i < numValues; i++) {
537 if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, rows[i]) < 0) {
538 return 0;
539 } else {
540 total++;
541 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800542 }
Winson Chungc763c4e2013-07-19 13:49:06 -0700543 db.setTransactionSuccessful();
544 } finally {
545 db.endTransaction();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800546 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800547 }
548
549 return total;
550 }
551
552 @Override
553 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Winson Chungc763c4e2013-07-19 13:49:06 -0700554 if (LOGD) Log.d(TAG, "onUpgrade triggered: " + oldVersion);
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700555
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800556 int version = oldVersion;
The Android Open Source Projectca9475f2009-03-13 13:04:24 -0700557 if (version < 3) {
558 // upgrade 1,2 -> 3 added appWidgetId column
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800559 db.beginTransaction();
560 try {
The Android Open Source Project7376fae2009-03-11 12:11:58 -0700561 // Insert new column for holding appWidgetIds
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800562 db.execSQL("ALTER TABLE favorites " +
The Android Open Source Projectca9475f2009-03-13 13:04:24 -0700563 "ADD COLUMN appWidgetId INTEGER NOT NULL DEFAULT -1;");
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800564 db.setTransactionSuccessful();
The Android Open Source Projectca9475f2009-03-13 13:04:24 -0700565 version = 3;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800566 } catch (SQLException ex) {
567 // Old version remains, which means we wipe old data
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800568 Log.e(TAG, ex.getMessage(), ex);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800569 } finally {
570 db.endTransaction();
571 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700572
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800573 // Convert existing widgets only if table upgrade was successful
The Android Open Source Projectca9475f2009-03-13 13:04:24 -0700574 if (version == 3) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800575 convertWidgets(db);
576 }
577 }
Romain Guy73b979d2009-06-09 12:57:21 -0700578
579 if (version < 4) {
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800580 version = 4;
Romain Guy73b979d2009-06-09 12:57:21 -0700581 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700582
Romain Guy509cd6a2010-03-23 15:10:56 -0700583 // Where's version 5?
584 // - Donut and sholes on 2.0 shipped with version 4 of launcher1.
Daniel Sandler325dc232013-06-05 22:57:57 -0400585 // - Passion shipped on 2.1 with version 6 of launcher3
Romain Guy509cd6a2010-03-23 15:10:56 -0700586 // - Sholes shipped on 2.1r1 (aka Mr. 3) with version 5 of launcher 1
587 // but version 5 on there was the updateContactsShortcuts change
588 // which was version 6 in launcher 2 (first shipped on passion 2.1r1).
589 // The updateContactsShortcuts change is idempotent, so running it twice
590 // is okay so we'll do that when upgrading the devices that shipped with it.
591 if (version < 6) {
Mike Cleron3a2b3f22009-11-05 17:17:42 -0800592 // We went from 3 to 5 screens. Move everything 1 to the right
593 db.beginTransaction();
594 try {
595 db.execSQL("UPDATE favorites SET screen=(screen + 1);");
596 db.setTransactionSuccessful();
Mike Cleron3a2b3f22009-11-05 17:17:42 -0800597 } catch (SQLException ex) {
598 // Old version remains, which means we wipe old data
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800599 Log.e(TAG, ex.getMessage(), ex);
Mike Cleron3a2b3f22009-11-05 17:17:42 -0800600 } finally {
601 db.endTransaction();
602 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700603
Romain Guy509cd6a2010-03-23 15:10:56 -0700604 // We added the fast track.
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800605 if (updateContactsShortcuts(db)) {
606 version = 6;
607 }
608 }
Bjorn Bringert7984c942009-12-09 15:38:25 +0000609
610 if (version < 7) {
611 // Version 7 gets rid of the special search widget.
612 convertWidgets(db);
613 version = 7;
614 }
615
Joe Onorato0589f0f2010-02-08 13:44:00 -0800616 if (version < 8) {
617 // Version 8 (froyo) has the icons all normalized. This should
618 // already be the case in practice, but we now rely on it and don't
619 // resample the images each time.
620 normalizeIcons(db);
621 version = 8;
622 }
623
Winson Chung3d503fb2011-07-13 17:25:49 -0700624 if (version < 9) {
625 // The max id is not yet set at this point (onUpgrade is triggered in the ctor
626 // 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 -0700627 if (mMaxItemId == -1) {
628 mMaxItemId = initializeMaxItemId(db);
Winson Chung3d503fb2011-07-13 17:25:49 -0700629 }
630
631 // Add default hotseat icons
Winson Chung6d092682011-11-16 18:43:26 -0800632 loadFavorites(db, R.xml.update_workspace);
Winson Chung3d503fb2011-07-13 17:25:49 -0700633 version = 9;
634 }
635
Daniel Lehmannd02402c2012-05-14 18:30:53 -0700636 // We bumped the version three time during JB, once to update the launch flags, once to
637 // update the override for the default launch animation and once to set the mimetype
638 // to improve startup performance
639 if (version < 12) {
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700640 // Contact shortcuts need a different set of flags to be launched now
641 // The updateContactsShortcuts change is idempotent, so we can keep using it like
642 // back in the Donut days
643 updateContactsShortcuts(db);
Daniel Lehmannd02402c2012-05-14 18:30:53 -0700644 version = 12;
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700645 }
646
Adam Cohendcd297f2013-06-18 13:13:40 -0700647 if (version < 13) {
648 // With the new shrink-wrapped and re-orderable workspaces, it makes sense
649 // to persist workspace screens and their relative order.
650 mMaxScreenId = 0;
651
652 // This will never happen in the wild, but when we switch to using workspace
653 // screen ids, redo the import from old launcher.
Winson Chungc763c4e2013-07-19 13:49:06 -0700654 sJustLoadedFromOldDb = true;
Adam Cohendcd297f2013-06-18 13:13:40 -0700655
656 addWorkspacesTable(db);
657 version = 13;
658 }
659
Chris Wrend5e66bf2013-09-16 14:02:29 -0400660 if (version < 14) {
661 db.beginTransaction();
662 try {
663 // Insert new column for holding widget provider name
664 db.execSQL("ALTER TABLE favorites " +
665 "ADD COLUMN appWidgetProvider TEXT;");
666 db.setTransactionSuccessful();
667 version = 14;
668 } catch (SQLException ex) {
669 // Old version remains, which means we wipe old data
670 Log.e(TAG, ex.getMessage(), ex);
671 } finally {
672 db.endTransaction();
673 }
674 }
675
Chris Wren1ada10d2013-09-13 18:01:38 -0400676
677 if (version < 15) {
678 db.beginTransaction();
679 try {
680 // Insert new column for holding update timestamp
681 db.execSQL("ALTER TABLE favorites " +
682 "ADD COLUMN modified INTEGER NOT NULL DEFAULT 0;");
683 db.execSQL("ALTER TABLE workspaceScreens " +
684 "ADD COLUMN modified INTEGER NOT NULL DEFAULT 0;");
685 db.setTransactionSuccessful();
686 version = 15;
687 } catch (SQLException ex) {
688 // Old version remains, which means we wipe old data
689 Log.e(TAG, ex.getMessage(), ex);
690 } finally {
691 db.endTransaction();
692 }
693 }
694
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800695 if (version != DATABASE_VERSION) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800696 Log.w(TAG, "Destroying all old data.");
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800697 db.execSQL("DROP TABLE IF EXISTS " + TABLE_FAVORITES);
Adam Cohendcd297f2013-06-18 13:13:40 -0700698 db.execSQL("DROP TABLE IF EXISTS " + TABLE_WORKSPACE_SCREENS);
699
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800700 onCreate(db);
701 }
702 }
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800703
704 private boolean updateContactsShortcuts(SQLiteDatabase db) {
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800705 final String selectWhere = buildOrWhereString(Favorites.ITEM_TYPE,
706 new int[] { Favorites.ITEM_TYPE_SHORTCUT });
707
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700708 Cursor c = null;
709 final String actionQuickContact = "com.android.contacts.action.QUICK_CONTACT";
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800710 db.beginTransaction();
711 try {
712 // Select and iterate through each matching widget
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700713 c = db.query(TABLE_FAVORITES,
714 new String[] { Favorites._ID, Favorites.INTENT },
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800715 selectWhere, null, null, null, null);
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700716 if (c == null) return false;
717
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800718 if (LOGD) Log.d(TAG, "found upgrade cursor count=" + c.getCount());
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700719
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800720 final int idIndex = c.getColumnIndex(Favorites._ID);
721 final int intentIndex = c.getColumnIndex(Favorites.INTENT);
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700722
723 while (c.moveToNext()) {
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800724 long favoriteId = c.getLong(idIndex);
725 final String intentUri = c.getString(intentIndex);
726 if (intentUri != null) {
727 try {
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700728 final Intent intent = Intent.parseUri(intentUri, 0);
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800729 android.util.Log.d("Home", intent.toString());
730 final Uri uri = intent.getData();
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700731 if (uri != null) {
732 final String data = uri.toString();
733 if ((Intent.ACTION_VIEW.equals(intent.getAction()) ||
734 actionQuickContact.equals(intent.getAction())) &&
735 (data.startsWith("content://contacts/people/") ||
736 data.startsWith("content://com.android.contacts/" +
737 "contacts/lookup/"))) {
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800738
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700739 final Intent newIntent = new Intent(actionQuickContact);
740 // When starting from the launcher, start in a new, cleared task
741 // CLEAR_WHEN_TASK_RESET cannot reset the root of a task, so we
742 // clear the whole thing preemptively here since
743 // QuickContactActivity will finish itself when launching other
744 // detail activities.
745 newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
746 Intent.FLAG_ACTIVITY_CLEAR_TASK);
Winson Chung2672ff92012-05-04 16:22:30 -0700747 newIntent.putExtra(
748 Launcher.INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION, true);
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700749 newIntent.setData(uri);
Daniel Lehmannd02402c2012-05-14 18:30:53 -0700750 // Determine the type and also put that in the shortcut
751 // (that can speed up launch a bit)
752 newIntent.setDataAndType(uri, newIntent.resolveType(mContext));
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800753
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700754 final ContentValues values = new ContentValues();
755 values.put(LauncherSettings.Favorites.INTENT,
756 newIntent.toUri(0));
757
758 String updateWhere = Favorites._ID + "=" + favoriteId;
759 db.update(TABLE_FAVORITES, values, updateWhere, null);
760 }
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800761 }
762 } catch (RuntimeException ex) {
763 Log.e(TAG, "Problem upgrading shortcut", ex);
764 } catch (URISyntaxException e) {
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700765 Log.e(TAG, "Problem upgrading shortcut", e);
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800766 }
767 }
768 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700769
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800770 db.setTransactionSuccessful();
771 } catch (SQLException ex) {
772 Log.w(TAG, "Problem while upgrading contacts", ex);
773 return false;
774 } finally {
775 db.endTransaction();
776 if (c != null) {
777 c.close();
778 }
779 }
780
781 return true;
782 }
783
Joe Onorato0589f0f2010-02-08 13:44:00 -0800784 private void normalizeIcons(SQLiteDatabase db) {
785 Log.d(TAG, "normalizing icons");
786
Joe Onorato346e1292010-02-18 10:34:24 -0500787 db.beginTransaction();
Joe Onorato0589f0f2010-02-08 13:44:00 -0800788 Cursor c = null;
Joe Onorato9690b392010-03-23 17:34:37 -0400789 SQLiteStatement update = null;
Joe Onorato0589f0f2010-02-08 13:44:00 -0800790 try {
791 boolean logged = false;
Joe Onorato9690b392010-03-23 17:34:37 -0400792 update = db.compileStatement("UPDATE favorites "
Jeff Hamiltoneaf77d62010-02-13 00:08:17 -0600793 + "SET icon=? WHERE _id=?");
Joe Onorato0589f0f2010-02-08 13:44:00 -0800794
795 c = db.rawQuery("SELECT _id, icon FROM favorites WHERE iconType=" +
796 Favorites.ICON_TYPE_BITMAP, null);
797
798 final int idIndex = c.getColumnIndexOrThrow(Favorites._ID);
799 final int iconIndex = c.getColumnIndexOrThrow(Favorites.ICON);
800
801 while (c.moveToNext()) {
802 long id = c.getLong(idIndex);
803 byte[] data = c.getBlob(iconIndex);
804 try {
805 Bitmap bitmap = Utilities.resampleIconBitmap(
806 BitmapFactory.decodeByteArray(data, 0, data.length),
807 mContext);
808 if (bitmap != null) {
809 update.bindLong(1, id);
810 data = ItemInfo.flattenBitmap(bitmap);
811 if (data != null) {
812 update.bindBlob(2, data);
813 update.execute();
814 }
815 bitmap.recycle();
Joe Onorato0589f0f2010-02-08 13:44:00 -0800816 }
817 } catch (Exception e) {
818 if (!logged) {
819 Log.e(TAG, "Failed normalizing icon " + id, e);
820 } else {
821 Log.e(TAG, "Also failed normalizing icon " + id);
822 }
823 logged = true;
824 }
825 }
Bjorn Bringert3a928e42010-02-19 11:15:40 +0000826 db.setTransactionSuccessful();
Joe Onorato0589f0f2010-02-08 13:44:00 -0800827 } catch (SQLException ex) {
828 Log.w(TAG, "Problem while allocating appWidgetIds for existing widgets", ex);
829 } finally {
830 db.endTransaction();
Joe Onorato9690b392010-03-23 17:34:37 -0400831 if (update != null) {
832 update.close();
833 }
Joe Onorato0589f0f2010-02-08 13:44:00 -0800834 if (c != null) {
835 c.close();
836 }
837 }
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700838 }
839
840 // Generates a new ID to use for an object in your database. This method should be only
841 // called from the main UI thread. As an exception, we do call it when we call the
842 // constructor from the worker thread; however, this doesn't extend until after the
843 // constructor is called, and we only pass a reference to LauncherProvider to LauncherApp
844 // after that point
Adam Cohendcd297f2013-06-18 13:13:40 -0700845 public long generateNewItemId() {
846 if (mMaxItemId < 0) {
847 throw new RuntimeException("Error: max item id was not initialized");
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700848 }
Adam Cohendcd297f2013-06-18 13:13:40 -0700849 mMaxItemId += 1;
850 return mMaxItemId;
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700851 }
852
Winson Chungc763c4e2013-07-19 13:49:06 -0700853 public void updateMaxItemId(long id) {
854 mMaxItemId = id + 1;
855 }
856
Adam Cohendcd297f2013-06-18 13:13:40 -0700857 private long initializeMaxItemId(SQLiteDatabase db) {
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700858 Cursor c = db.rawQuery("SELECT MAX(_id) FROM favorites", null);
859
860 // get the result
861 final int maxIdIndex = 0;
862 long id = -1;
863 if (c != null && c.moveToNext()) {
864 id = c.getLong(maxIdIndex);
865 }
Michael Jurka5130e402011-10-13 04:55:35 -0700866 if (c != null) {
867 c.close();
868 }
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700869
870 if (id == -1) {
Adam Cohendcd297f2013-06-18 13:13:40 -0700871 throw new RuntimeException("Error: could not query max item id");
872 }
873
874 return id;
875 }
876
877 // Generates a new ID to use for an workspace screen in your database. This method
878 // should be only called from the main UI thread. As an exception, we do call it when we
879 // call the constructor from the worker thread; however, this doesn't extend until after the
880 // constructor is called, and we only pass a reference to LauncherProvider to LauncherApp
881 // after that point
882 public long generateNewScreenId() {
883 if (mMaxScreenId < 0) {
884 throw new RuntimeException("Error: max screen id was not initialized");
885 }
886 mMaxScreenId += 1;
Winson Chunga90303b2013-11-15 13:05:06 -0800887 // Log to disk
888 Launcher.addDumpLog(TAG, "11683562 - generateNewScreenId(): " + mMaxScreenId, true);
Adam Cohendcd297f2013-06-18 13:13:40 -0700889 return mMaxScreenId;
890 }
891
892 public void updateMaxScreenId(long maxScreenId) {
Winson Chunga90303b2013-11-15 13:05:06 -0800893 // Log to disk
894 Launcher.addDumpLog(TAG, "11683562 - updateMaxScreenId(): " + maxScreenId, true);
Adam Cohendcd297f2013-06-18 13:13:40 -0700895 mMaxScreenId = maxScreenId;
896 }
897
898 private long initializeMaxScreenId(SQLiteDatabase db) {
899 Cursor c = db.rawQuery("SELECT MAX(" + LauncherSettings.WorkspaceScreens._ID + ") FROM " + TABLE_WORKSPACE_SCREENS, null);
900
901 // get the result
902 final int maxIdIndex = 0;
903 long id = -1;
904 if (c != null && c.moveToNext()) {
905 id = c.getLong(maxIdIndex);
906 }
907 if (c != null) {
908 c.close();
909 }
910
911 if (id == -1) {
912 throw new RuntimeException("Error: could not query max screen id");
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700913 }
914
Winson Chunga90303b2013-11-15 13:05:06 -0800915 // Log to disk
916 Launcher.addDumpLog(TAG, "11683562 - initializeMaxScreenId(): " + id, true);
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700917 return id;
Joe Onorato0589f0f2010-02-08 13:44:00 -0800918 }
919
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800920 /**
The Android Open Source Project7376fae2009-03-11 12:11:58 -0700921 * Upgrade existing clock and photo frame widgets into their new widget
Bjorn Bringert93c45762009-12-16 13:19:47 +0000922 * equivalents.
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800923 */
924 private void convertWidgets(SQLiteDatabase db) {
Bjorn Bringert34251342009-12-15 13:33:11 +0000925 final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800926 final int[] bindSources = new int[] {
927 Favorites.ITEM_TYPE_WIDGET_CLOCK,
928 Favorites.ITEM_TYPE_WIDGET_PHOTO_FRAME,
Bjorn Bringert7984c942009-12-09 15:38:25 +0000929 Favorites.ITEM_TYPE_WIDGET_SEARCH,
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800930 };
Bjorn Bringert7984c942009-12-09 15:38:25 +0000931
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800932 final String selectWhere = buildOrWhereString(Favorites.ITEM_TYPE, bindSources);
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700933
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800934 Cursor c = null;
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700935
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800936 db.beginTransaction();
937 try {
938 // Select and iterate through each matching widget
Bjorn Bringert7984c942009-12-09 15:38:25 +0000939 c = db.query(TABLE_FAVORITES, new String[] { Favorites._ID, Favorites.ITEM_TYPE },
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800940 selectWhere, null, null, null, null);
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700941
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800942 if (LOGD) Log.d(TAG, "found upgrade cursor count=" + c.getCount());
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700943
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800944 final ContentValues values = new ContentValues();
945 while (c != null && c.moveToNext()) {
946 long favoriteId = c.getLong(0);
Bjorn Bringert7984c942009-12-09 15:38:25 +0000947 int favoriteType = c.getInt(1);
948
The Android Open Source Project7376fae2009-03-11 12:11:58 -0700949 // Allocate and update database with new appWidgetId
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800950 try {
The Android Open Source Project7376fae2009-03-11 12:11:58 -0700951 int appWidgetId = mAppWidgetHost.allocateAppWidgetId();
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700952
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800953 if (LOGD) {
954 Log.d(TAG, "allocated appWidgetId=" + appWidgetId
955 + " for favoriteId=" + favoriteId);
956 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800957 values.clear();
Bjorn Bringert7984c942009-12-09 15:38:25 +0000958 values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_APPWIDGET);
959 values.put(Favorites.APPWIDGET_ID, appWidgetId);
960
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800961 // Original widgets might not have valid spans when upgrading
Bjorn Bringert7984c942009-12-09 15:38:25 +0000962 if (favoriteType == Favorites.ITEM_TYPE_WIDGET_SEARCH) {
963 values.put(LauncherSettings.Favorites.SPANX, 4);
964 values.put(LauncherSettings.Favorites.SPANY, 1);
965 } else {
966 values.put(LauncherSettings.Favorites.SPANX, 2);
967 values.put(LauncherSettings.Favorites.SPANY, 2);
968 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800969
970 String updateWhere = Favorites._ID + "=" + favoriteId;
971 db.update(TABLE_FAVORITES, values, updateWhere, null);
Bjorn Bringert34251342009-12-15 13:33:11 +0000972
Bjorn Bringert34251342009-12-15 13:33:11 +0000973 if (favoriteType == Favorites.ITEM_TYPE_WIDGET_CLOCK) {
Michael Jurka8b805b12012-04-18 14:23:14 -0700974 // TODO: check return value
975 appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId,
Bjorn Bringert34251342009-12-15 13:33:11 +0000976 new ComponentName("com.android.alarmclock",
977 "com.android.alarmclock.AnalogAppWidgetProvider"));
978 } else if (favoriteType == Favorites.ITEM_TYPE_WIDGET_PHOTO_FRAME) {
Michael Jurka8b805b12012-04-18 14:23:14 -0700979 // TODO: check return value
980 appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId,
Bjorn Bringert34251342009-12-15 13:33:11 +0000981 new ComponentName("com.android.camera",
982 "com.android.camera.PhotoAppWidgetProvider"));
983 } else if (favoriteType == Favorites.ITEM_TYPE_WIDGET_SEARCH) {
Michael Jurka8b805b12012-04-18 14:23:14 -0700984 // TODO: check return value
985 appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId,
Bjorn Bringertcd8fec02010-01-14 13:26:43 +0000986 getSearchWidgetProvider());
Bjorn Bringert34251342009-12-15 13:33:11 +0000987 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800988 } catch (RuntimeException ex) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800989 Log.e(TAG, "Problem allocating appWidgetId", ex);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800990 }
991 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700992
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800993 db.setTransactionSuccessful();
994 } catch (SQLException ex) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800995 Log.w(TAG, "Problem while allocating appWidgetIds for existing widgets", ex);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800996 } finally {
997 db.endTransaction();
998 if (c != null) {
999 c.close();
1000 }
1001 }
Winson Chungc763c4e2013-07-19 13:49:06 -07001002
1003 // Update max item id
1004 mMaxItemId = initializeMaxItemId(db);
1005 if (LOGD) Log.d(TAG, "mMaxItemId: " + mMaxItemId);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001006 }
1007
Michael Jurka8b805b12012-04-18 14:23:14 -07001008 private static final void beginDocument(XmlPullParser parser, String firstElementName)
1009 throws XmlPullParserException, IOException {
1010 int type;
Michael Jurka9bc8eba2012-05-21 20:36:44 -07001011 while ((type = parser.next()) != XmlPullParser.START_TAG
1012 && type != XmlPullParser.END_DOCUMENT) {
Michael Jurka8b805b12012-04-18 14:23:14 -07001013 ;
1014 }
1015
Michael Jurka9bc8eba2012-05-21 20:36:44 -07001016 if (type != XmlPullParser.START_TAG) {
Michael Jurka8b805b12012-04-18 14:23:14 -07001017 throw new XmlPullParserException("No start tag found");
1018 }
1019
1020 if (!parser.getName().equals(firstElementName)) {
1021 throw new XmlPullParserException("Unexpected start tag: found " + parser.getName() +
1022 ", expected " + firstElementName);
1023 }
1024 }
1025
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001026 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001027 * Loads the default set of favorite packages from an xml file.
1028 *
1029 * @param db The database to write the values into
Winson Chung3d503fb2011-07-13 17:25:49 -07001030 * @param filterContainerId The specific container id of items to load
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001031 */
Winson Chung6d092682011-11-16 18:43:26 -08001032 private int loadFavorites(SQLiteDatabase db, int workspaceResourceId) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001033 Intent intent = new Intent(Intent.ACTION_MAIN, null);
1034 intent.addCategory(Intent.CATEGORY_LAUNCHER);
1035 ContentValues values = new ContentValues();
1036
Daniel Sandler57dac262013-10-03 13:28:36 -04001037 if (LOGD) Log.v(TAG, String.format("Loading favorites from resid=0x%08x", workspaceResourceId));
1038
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001039 PackageManager packageManager = mContext.getPackageManager();
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001040 int i = 0;
1041 try {
Winson Chung6d092682011-11-16 18:43:26 -08001042 XmlResourceParser parser = mContext.getResources().getXml(workspaceResourceId);
The Android Open Source Projectf96811c2009-03-18 17:39:48 -07001043 AttributeSet attrs = Xml.asAttributeSet(parser);
Michael Jurka8b805b12012-04-18 14:23:14 -07001044 beginDocument(parser, TAG_FAVORITES);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001045
The Android Open Source Projectf96811c2009-03-18 17:39:48 -07001046 final int depth = parser.getDepth();
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001047
The Android Open Source Projectf96811c2009-03-18 17:39:48 -07001048 int type;
1049 while (((type = parser.next()) != XmlPullParser.END_TAG ||
1050 parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
1051
1052 if (type != XmlPullParser.START_TAG) {
1053 continue;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001054 }
1055
The Android Open Source Projectf96811c2009-03-18 17:39:48 -07001056 boolean added = false;
1057 final String name = parser.getName();
1058
Daniel Sandler57dac262013-10-03 13:28:36 -04001059 if (TAG_INCLUDE.equals(name)) {
1060 final TypedArray a = mContext.obtainStyledAttributes(attrs, R.styleable.Include);
1061
1062 final int resId = a.getResourceId(R.styleable.Include_workspace, 0);
1063
1064 if (LOGD) Log.v(TAG, String.format(("%" + (2*(depth+1)) + "s<include workspace=%08x>"),
1065 "", resId));
1066
1067 if (resId != 0 && resId != workspaceResourceId) {
1068 // recursively load some more favorites, why not?
1069 i += loadFavorites(db, resId);
1070 added = false;
Daniel Sandler57dac262013-10-03 13:28:36 -04001071 } else {
1072 Log.w(TAG, String.format("Skipping <include workspace=0x%08x>", resId));
1073 }
1074
1075 a.recycle();
1076
1077 if (LOGD) Log.v(TAG, String.format(("%" + (2*(depth+1)) + "s</include>"), ""));
1078 continue;
1079 }
1080
1081 // Assuming it's a <favorite> at this point
The Android Open Source Projectf96811c2009-03-18 17:39:48 -07001082 TypedArray a = mContext.obtainStyledAttributes(attrs, R.styleable.Favorite);
1083
Winson Chung3d503fb2011-07-13 17:25:49 -07001084 long container = LauncherSettings.Favorites.CONTAINER_DESKTOP;
1085 if (a.hasValue(R.styleable.Favorite_container)) {
1086 container = Long.valueOf(a.getString(R.styleable.Favorite_container));
1087 }
The Android Open Source Projectf96811c2009-03-18 17:39:48 -07001088
Winson Chung6d092682011-11-16 18:43:26 -08001089 String screen = a.getString(R.styleable.Favorite_screen);
1090 String x = a.getString(R.styleable.Favorite_x);
1091 String y = a.getString(R.styleable.Favorite_y);
1092
Winson Chung6d092682011-11-16 18:43:26 -08001093 values.clear();
1094 values.put(LauncherSettings.Favorites.CONTAINER, container);
1095 values.put(LauncherSettings.Favorites.SCREEN, screen);
1096 values.put(LauncherSettings.Favorites.CELLX, x);
1097 values.put(LauncherSettings.Favorites.CELLY, y);
1098
Daniel Sandler57dac262013-10-03 13:28:36 -04001099 if (LOGD) {
1100 final String title = a.getString(R.styleable.Favorite_title);
1101 final String pkg = a.getString(R.styleable.Favorite_packageName);
1102 final String something = title != null ? title : pkg;
1103 Log.v(TAG, String.format(
1104 ("%" + (2*(depth+1)) + "s<%s%s c=%d s=%s x=%s y=%s>"),
1105 "", name,
1106 (something == null ? "" : (" \"" + something + "\"")),
1107 container, screen, x, y));
1108 }
1109
Winson Chung6d092682011-11-16 18:43:26 -08001110 if (TAG_FAVORITE.equals(name)) {
1111 long id = addAppShortcut(db, values, a, packageManager, intent);
1112 added = id >= 0;
1113 } else if (TAG_SEARCH.equals(name)) {
1114 added = addSearchWidget(db, values);
1115 } else if (TAG_CLOCK.equals(name)) {
1116 added = addClockWidget(db, values);
1117 } else if (TAG_APPWIDGET.equals(name)) {
Winson Chungb3302ae2012-05-01 10:19:14 -07001118 added = addAppWidget(parser, attrs, type, db, values, a, packageManager);
Winson Chung6d092682011-11-16 18:43:26 -08001119 } else if (TAG_SHORTCUT.equals(name)) {
1120 long id = addUriShortcut(db, values, a);
1121 added = id >= 0;
1122 } else if (TAG_FOLDER.equals(name)) {
1123 String title;
1124 int titleResId = a.getResourceId(R.styleable.Favorite_title, -1);
1125 if (titleResId != -1) {
1126 title = mContext.getResources().getString(titleResId);
1127 } else {
1128 title = mContext.getResources().getString(R.string.folder_name);
Winson Chung3d503fb2011-07-13 17:25:49 -07001129 }
Winson Chung6d092682011-11-16 18:43:26 -08001130 values.put(LauncherSettings.Favorites.TITLE, title);
1131 long folderId = addFolder(db, values);
1132 added = folderId >= 0;
Winson Chung3d503fb2011-07-13 17:25:49 -07001133
Winson Chung6d092682011-11-16 18:43:26 -08001134 ArrayList<Long> folderItems = new ArrayList<Long>();
Winson Chung3d503fb2011-07-13 17:25:49 -07001135
Winson Chung6d092682011-11-16 18:43:26 -08001136 int folderDepth = parser.getDepth();
1137 while ((type = parser.next()) != XmlPullParser.END_TAG ||
1138 parser.getDepth() > folderDepth) {
1139 if (type != XmlPullParser.START_TAG) {
1140 continue;
1141 }
1142 final String folder_item_name = parser.getName();
1143
1144 TypedArray ar = mContext.obtainStyledAttributes(attrs,
1145 R.styleable.Favorite);
1146 values.clear();
1147 values.put(LauncherSettings.Favorites.CONTAINER, folderId);
1148
Daniel Sandler57dac262013-10-03 13:28:36 -04001149 if (LOGD) {
1150 final String pkg = ar.getString(R.styleable.Favorite_packageName);
1151 final String uri = ar.getString(R.styleable.Favorite_uri);
1152 Log.v(TAG, String.format(("%" + (2*(folderDepth+1)) + "s<%s \"%s\">"), "",
1153 folder_item_name, uri != null ? uri : pkg));
1154 }
1155
Winson Chung6d092682011-11-16 18:43:26 -08001156 if (TAG_FAVORITE.equals(folder_item_name) && folderId >= 0) {
1157 long id =
1158 addAppShortcut(db, values, ar, packageManager, intent);
1159 if (id >= 0) {
1160 folderItems.add(id);
1161 }
1162 } else if (TAG_SHORTCUT.equals(folder_item_name) && folderId >= 0) {
1163 long id = addUriShortcut(db, values, ar);
1164 if (id >= 0) {
1165 folderItems.add(id);
1166 }
Adam Cohen228da5a2011-07-27 22:23:47 -07001167 } else {
Winson Chung6d092682011-11-16 18:43:26 -08001168 throw new RuntimeException("Folders can " +
1169 "contain only shortcuts");
Adam Cohen228da5a2011-07-27 22:23:47 -07001170 }
Winson Chung6d092682011-11-16 18:43:26 -08001171 ar.recycle();
1172 }
1173 // We can only have folders with >= 2 items, so we need to remove the
1174 // folder and clean up if less than 2 items were included, or some
1175 // failed to add, and less than 2 were actually added
1176 if (folderItems.size() < 2 && folderId >= 0) {
1177 // We just delete the folder and any items that made it
1178 deleteId(db, folderId);
1179 if (folderItems.size() > 0) {
1180 deleteId(db, folderItems.get(0));
Adam Cohen228da5a2011-07-27 22:23:47 -07001181 }
Winson Chung6d092682011-11-16 18:43:26 -08001182 added = false;
Winson Chung3d503fb2011-07-13 17:25:49 -07001183 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001184 }
The Android Open Source Projectf96811c2009-03-18 17:39:48 -07001185 if (added) i++;
The Android Open Source Projectf96811c2009-03-18 17:39:48 -07001186 a.recycle();
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001187 }
1188 } catch (XmlPullParserException e) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -08001189 Log.w(TAG, "Got exception parsing favorites.", e);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001190 } catch (IOException e) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -08001191 Log.w(TAG, "Got exception parsing favorites.", e);
Winson Chung3d503fb2011-07-13 17:25:49 -07001192 } catch (RuntimeException e) {
1193 Log.w(TAG, "Got exception parsing favorites.", e);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001194 }
The Android Open Source Projectf96811c2009-03-18 17:39:48 -07001195
Winson Chungc763c4e2013-07-19 13:49:06 -07001196 // Update the max item id after we have loaded the database
1197 if (mMaxItemId == -1) {
1198 mMaxItemId = initializeMaxItemId(db);
1199 }
1200
The Android Open Source Projectf96811c2009-03-18 17:39:48 -07001201 return i;
1202 }
1203
Adam Cohen228da5a2011-07-27 22:23:47 -07001204 private long addAppShortcut(SQLiteDatabase db, ContentValues values, TypedArray a,
The Android Open Source Projectf96811c2009-03-18 17:39:48 -07001205 PackageManager packageManager, Intent intent) {
Adam Cohen228da5a2011-07-27 22:23:47 -07001206 long id = -1;
The Android Open Source Projectf96811c2009-03-18 17:39:48 -07001207 ActivityInfo info;
1208 String packageName = a.getString(R.styleable.Favorite_packageName);
1209 String className = a.getString(R.styleable.Favorite_className);
1210 try {
Romain Guy693599f2010-03-23 10:58:18 -07001211 ComponentName cn;
1212 try {
1213 cn = new ComponentName(packageName, className);
1214 info = packageManager.getActivityInfo(cn, 0);
1215 } catch (PackageManager.NameNotFoundException nnfe) {
1216 String[] packages = packageManager.currentToCanonicalPackageNames(
1217 new String[] { packageName });
1218 cn = new ComponentName(packages[0], className);
1219 info = packageManager.getActivityInfo(cn, 0);
1220 }
Adam Cohendcd297f2013-06-18 13:13:40 -07001221 id = generateNewItemId();
The Android Open Source Projectf96811c2009-03-18 17:39:48 -07001222 intent.setComponent(cn);
Romain Guy693599f2010-03-23 10:58:18 -07001223 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
1224 Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
Romain Guy1ce1a242009-06-23 17:34:54 -07001225 values.put(Favorites.INTENT, intent.toUri(0));
The Android Open Source Projectf96811c2009-03-18 17:39:48 -07001226 values.put(Favorites.TITLE, info.loadLabel(packageManager).toString());
1227 values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_APPLICATION);
1228 values.put(Favorites.SPANX, 1);
1229 values.put(Favorites.SPANY, 1);
Adam Cohendcd297f2013-06-18 13:13:40 -07001230 values.put(Favorites._ID, generateNewItemId());
Adam Cohen228da5a2011-07-27 22:23:47 -07001231 if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values) < 0) {
1232 return -1;
1233 }
The Android Open Source Projectf96811c2009-03-18 17:39:48 -07001234 } catch (PackageManager.NameNotFoundException e) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -08001235 Log.w(TAG, "Unable to add favorite: " + packageName +
The Android Open Source Projectf96811c2009-03-18 17:39:48 -07001236 "/" + className, e);
The Android Open Source Projectf96811c2009-03-18 17:39:48 -07001237 }
Adam Cohen228da5a2011-07-27 22:23:47 -07001238 return id;
1239 }
1240
1241 private long addFolder(SQLiteDatabase db, ContentValues values) {
1242 values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_FOLDER);
1243 values.put(Favorites.SPANX, 1);
1244 values.put(Favorites.SPANY, 1);
Adam Cohendcd297f2013-06-18 13:13:40 -07001245 long id = generateNewItemId();
Adam Cohen228da5a2011-07-27 22:23:47 -07001246 values.put(Favorites._ID, id);
1247 if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values) <= 0) {
1248 return -1;
1249 } else {
1250 return id;
1251 }
The Android Open Source Projectf96811c2009-03-18 17:39:48 -07001252 }
1253
Bjorn Bringertcd8fec02010-01-14 13:26:43 +00001254 private ComponentName getSearchWidgetProvider() {
1255 SearchManager searchManager =
1256 (SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE);
1257 ComponentName searchComponent = searchManager.getGlobalSearchActivity();
1258 if (searchComponent == null) return null;
1259 return getProviderInPackage(searchComponent.getPackageName());
1260 }
1261
1262 /**
1263 * Gets an appwidget provider from the given package. If the package contains more than
1264 * one appwidget provider, an arbitrary one is returned.
1265 */
1266 private ComponentName getProviderInPackage(String packageName) {
1267 AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext);
1268 List<AppWidgetProviderInfo> providers = appWidgetManager.getInstalledProviders();
1269 if (providers == null) return null;
1270 final int providerCount = providers.size();
1271 for (int i = 0; i < providerCount; i++) {
1272 ComponentName provider = providers.get(i).provider;
1273 if (provider != null && provider.getPackageName().equals(packageName)) {
1274 return provider;
1275 }
1276 }
1277 return null;
1278 }
1279
The Android Open Source Projectf96811c2009-03-18 17:39:48 -07001280 private boolean addSearchWidget(SQLiteDatabase db, ContentValues values) {
Bjorn Bringertcd8fec02010-01-14 13:26:43 +00001281 ComponentName cn = getSearchWidgetProvider();
Winson Chungb3302ae2012-05-01 10:19:14 -07001282 return addAppWidget(db, values, cn, 4, 1, null);
The Android Open Source Projectf96811c2009-03-18 17:39:48 -07001283 }
1284
1285 private boolean addClockWidget(SQLiteDatabase db, ContentValues values) {
Bjorn Bringert34251342009-12-15 13:33:11 +00001286 ComponentName cn = new ComponentName("com.android.alarmclock",
1287 "com.android.alarmclock.AnalogAppWidgetProvider");
Winson Chungb3302ae2012-05-01 10:19:14 -07001288 return addAppWidget(db, values, cn, 2, 2, null);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001289 }
Adam Cohen228da5a2011-07-27 22:23:47 -07001290
Winson Chungb3302ae2012-05-01 10:19:14 -07001291 private boolean addAppWidget(XmlResourceParser parser, AttributeSet attrs, int type,
1292 SQLiteDatabase db, ContentValues values, TypedArray a,
1293 PackageManager packageManager) throws XmlPullParserException, IOException {
Romain Guy693599f2010-03-23 10:58:18 -07001294
Mike Cleronb87bd162009-10-30 16:36:56 -07001295 String packageName = a.getString(R.styleable.Favorite_packageName);
1296 String className = a.getString(R.styleable.Favorite_className);
1297
1298 if (packageName == null || className == null) {
1299 return false;
1300 }
Romain Guy693599f2010-03-23 10:58:18 -07001301
1302 boolean hasPackage = true;
Mike Cleronb87bd162009-10-30 16:36:56 -07001303 ComponentName cn = new ComponentName(packageName, className);
Romain Guy693599f2010-03-23 10:58:18 -07001304 try {
1305 packageManager.getReceiverInfo(cn, 0);
1306 } catch (Exception e) {
1307 String[] packages = packageManager.currentToCanonicalPackageNames(
1308 new String[] { packageName });
1309 cn = new ComponentName(packages[0], className);
1310 try {
1311 packageManager.getReceiverInfo(cn, 0);
1312 } catch (Exception e1) {
1313 hasPackage = false;
1314 }
1315 }
1316
1317 if (hasPackage) {
1318 int spanX = a.getInt(R.styleable.Favorite_spanX, 0);
1319 int spanY = a.getInt(R.styleable.Favorite_spanY, 0);
Winson Chungb3302ae2012-05-01 10:19:14 -07001320
1321 // Read the extras
1322 Bundle extras = new Bundle();
1323 int widgetDepth = parser.getDepth();
1324 while ((type = parser.next()) != XmlPullParser.END_TAG ||
1325 parser.getDepth() > widgetDepth) {
1326 if (type != XmlPullParser.START_TAG) {
1327 continue;
1328 }
1329
1330 TypedArray ar = mContext.obtainStyledAttributes(attrs, R.styleable.Extra);
1331 if (TAG_EXTRA.equals(parser.getName())) {
1332 String key = ar.getString(R.styleable.Extra_key);
1333 String value = ar.getString(R.styleable.Extra_value);
1334 if (key != null && value != null) {
1335 extras.putString(key, value);
1336 } else {
1337 throw new RuntimeException("Widget extras must have a key and value");
1338 }
1339 } else {
1340 throw new RuntimeException("Widgets can contain only extras");
1341 }
1342 ar.recycle();
1343 }
1344
1345 return addAppWidget(db, values, cn, spanX, spanY, extras);
Romain Guy693599f2010-03-23 10:58:18 -07001346 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -07001347
Romain Guy693599f2010-03-23 10:58:18 -07001348 return false;
Bjorn Bringert7984c942009-12-09 15:38:25 +00001349 }
Bjorn Bringert7984c942009-12-09 15:38:25 +00001350 private boolean addAppWidget(SQLiteDatabase db, ContentValues values, ComponentName cn,
Winson Chungb3302ae2012-05-01 10:19:14 -07001351 int spanX, int spanY, Bundle extras) {
Mike Cleronb87bd162009-10-30 16:36:56 -07001352 boolean allocatedAppWidgets = false;
1353 final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext);
1354
1355 try {
1356 int appWidgetId = mAppWidgetHost.allocateAppWidgetId();
Daniel Lehmannc3a80402012-04-23 21:35:11 -07001357
Mike Cleronb87bd162009-10-30 16:36:56 -07001358 values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_APPWIDGET);
Bjorn Bringert7984c942009-12-09 15:38:25 +00001359 values.put(Favorites.SPANX, spanX);
1360 values.put(Favorites.SPANY, spanY);
Mike Cleronb87bd162009-10-30 16:36:56 -07001361 values.put(Favorites.APPWIDGET_ID, appWidgetId);
Chris Wrend5e66bf2013-09-16 14:02:29 -04001362 values.put(Favorites.APPWIDGET_PROVIDER, cn.flattenToString());
Adam Cohendcd297f2013-06-18 13:13:40 -07001363 values.put(Favorites._ID, generateNewItemId());
Michael Jurkaa8c760d2011-04-28 14:59:33 -07001364 dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values);
Mike Cleronb87bd162009-10-30 16:36:56 -07001365
1366 allocatedAppWidgets = true;
Daniel Lehmannc3a80402012-04-23 21:35:11 -07001367
Michael Jurka8b805b12012-04-18 14:23:14 -07001368 // TODO: need to check return value
1369 appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, cn);
Winson Chungb3302ae2012-05-01 10:19:14 -07001370
1371 // Send a broadcast to configure the widget
1372 if (extras != null && !extras.isEmpty()) {
1373 Intent intent = new Intent(ACTION_APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE);
1374 intent.setComponent(cn);
1375 intent.putExtras(extras);
1376 intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
1377 mContext.sendBroadcast(intent);
1378 }
Mike Cleronb87bd162009-10-30 16:36:56 -07001379 } catch (RuntimeException ex) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -08001380 Log.e(TAG, "Problem allocating appWidgetId", ex);
Mike Cleronb87bd162009-10-30 16:36:56 -07001381 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -07001382
Mike Cleronb87bd162009-10-30 16:36:56 -07001383 return allocatedAppWidgets;
1384 }
Adam Cohen228da5a2011-07-27 22:23:47 -07001385
1386 private long addUriShortcut(SQLiteDatabase db, ContentValues values,
Mike Cleronb87bd162009-10-30 16:36:56 -07001387 TypedArray a) {
1388 Resources r = mContext.getResources();
1389
1390 final int iconResId = a.getResourceId(R.styleable.Favorite_icon, 0);
1391 final int titleResId = a.getResourceId(R.styleable.Favorite_title, 0);
1392
Romain Guy7eb9e5e2009-12-02 20:10:07 -08001393 Intent intent;
Mike Cleronb87bd162009-10-30 16:36:56 -07001394 String uri = null;
1395 try {
1396 uri = a.getString(R.styleable.Favorite_uri);
1397 intent = Intent.parseUri(uri, 0);
1398 } catch (URISyntaxException e) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -08001399 Log.w(TAG, "Shortcut has malformed uri: " + uri);
Adam Cohen228da5a2011-07-27 22:23:47 -07001400 return -1; // Oh well
Mike Cleronb87bd162009-10-30 16:36:56 -07001401 }
1402
1403 if (iconResId == 0 || titleResId == 0) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -08001404 Log.w(TAG, "Shortcut is missing title or icon resource ID");
Adam Cohen228da5a2011-07-27 22:23:47 -07001405 return -1;
Mike Cleronb87bd162009-10-30 16:36:56 -07001406 }
1407
Adam Cohendcd297f2013-06-18 13:13:40 -07001408 long id = generateNewItemId();
Mike Cleronb87bd162009-10-30 16:36:56 -07001409 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1410 values.put(Favorites.INTENT, intent.toUri(0));
1411 values.put(Favorites.TITLE, r.getString(titleResId));
1412 values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_SHORTCUT);
1413 values.put(Favorites.SPANX, 1);
1414 values.put(Favorites.SPANY, 1);
1415 values.put(Favorites.ICON_TYPE, Favorites.ICON_TYPE_RESOURCE);
1416 values.put(Favorites.ICON_PACKAGE, mContext.getPackageName());
1417 values.put(Favorites.ICON_RESOURCE, r.getResourceName(iconResId));
Adam Cohen228da5a2011-07-27 22:23:47 -07001418 values.put(Favorites._ID, id);
Mike Cleronb87bd162009-10-30 16:36:56 -07001419
Adam Cohen228da5a2011-07-27 22:23:47 -07001420 if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values) < 0) {
1421 return -1;
1422 }
1423 return id;
Mike Cleronb87bd162009-10-30 16:36:56 -07001424 }
1425 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -07001426
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001427 /**
1428 * Build a query string that will match any row where the column matches
1429 * anything in the values list.
1430 */
1431 static String buildOrWhereString(String column, int[] values) {
1432 StringBuilder selectWhere = new StringBuilder();
1433 for (int i = values.length - 1; i >= 0; i--) {
1434 selectWhere.append(column).append("=").append(values[i]);
1435 if (i > 0) {
1436 selectWhere.append(" OR ");
1437 }
1438 }
1439 return selectWhere.toString();
1440 }
1441
1442 static class SqlArguments {
1443 public final String table;
1444 public final String where;
1445 public final String[] args;
1446
1447 SqlArguments(Uri url, String where, String[] args) {
1448 if (url.getPathSegments().size() == 1) {
1449 this.table = url.getPathSegments().get(0);
1450 this.where = where;
1451 this.args = args;
1452 } else if (url.getPathSegments().size() != 2) {
1453 throw new IllegalArgumentException("Invalid URI: " + url);
1454 } else if (!TextUtils.isEmpty(where)) {
1455 throw new UnsupportedOperationException("WHERE clause not supported: " + url);
1456 } else {
1457 this.table = url.getPathSegments().get(0);
Daniel Lehmannc3a80402012-04-23 21:35:11 -07001458 this.where = "_id=" + ContentUris.parseId(url);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001459 this.args = null;
1460 }
1461 }
1462
1463 SqlArguments(Uri url) {
1464 if (url.getPathSegments().size() == 1) {
1465 table = url.getPathSegments().get(0);
1466 where = null;
1467 args = null;
1468 } else {
1469 throw new IllegalArgumentException("Invalid URI: " + url);
1470 }
1471 }
1472 }
1473}