blob: 7d090e1fd45731abbf2ea8956c3a60d9bf9a6c45 [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;
Michael Jurka8b805b12012-04-18 14:23:14 -070053
54import org.xmlpull.v1.XmlPullParser;
55import org.xmlpull.v1.XmlPullParserException;
56
The Android Open Source Project31dd5032009-03-03 19:32:27 -080057import java.io.IOException;
Mike Cleronb87bd162009-10-30 16:36:56 -070058import java.net.URISyntaxException;
Adam Cohen228da5a2011-07-27 22:23:47 -070059import java.util.ArrayList;
Bjorn Bringertcd8fec02010-01-14 13:26:43 +000060import java.util.List;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080061
The Android Open Source Project31dd5032009-03-03 19:32:27 -080062public class LauncherProvider extends ContentProvider {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -080063 private static final String TAG = "Launcher.LauncherProvider";
64 private static final boolean LOGD = false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080065
66 private static final String DATABASE_NAME = "launcher.db";
Winson Chung3d503fb2011-07-13 17:25:49 -070067
Adam Cohendcd297f2013-06-18 13:13:40 -070068 private static final int DATABASE_VERSION = 13;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080069
Adam Cohene25af792013-06-06 23:08:25 -070070 static final String OLD_AUTHORITY = "com.android.launcher2.settings";
Daniel Sandler325dc232013-06-05 22:57:57 -040071 static final String AUTHORITY = "com.android.launcher3.settings";
Winson Chung3d503fb2011-07-13 17:25:49 -070072
The Android Open Source Project31dd5032009-03-03 19:32:27 -080073 static final String TABLE_FAVORITES = "favorites";
Adam Cohendcd297f2013-06-18 13:13:40 -070074 static final String TABLE_WORKSPACE_SCREENS = "workspaceScreens";
The Android Open Source Project31dd5032009-03-03 19:32:27 -080075 static final String PARAMETER_NOTIFY = "notify";
Winson Chungc763c4e2013-07-19 13:49:06 -070076 static final String UPGRADED_FROM_OLD_DATABASE =
77 "UPGRADED_FROM_OLD_DATABASE";
78 static final String EMPTY_DATABASE_CREATED =
79 "EMPTY_DATABASE_CREATED";
Michael Jurka45355c42012-10-08 13:21:35 +020080 static final String DEFAULT_WORKSPACE_RESOURCE_ID =
81 "DEFAULT_WORKSPACE_RESOURCE_ID";
The Android Open Source Project31dd5032009-03-03 19:32:27 -080082
Winson Chungb3302ae2012-05-01 10:19:14 -070083 private static final String ACTION_APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE =
Adam Cohene25af792013-06-06 23:08:25 -070084 "com.android.launcher.action.APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE";
Winson Chungb3302ae2012-05-01 10:19:14 -070085
Jeffrey Sharkey2bbcae12009-03-31 14:37:57 -070086 /**
Romain Guy73b979d2009-06-09 12:57:21 -070087 * {@link Uri} triggered at any registered {@link android.database.ContentObserver} when
Jeffrey Sharkey2bbcae12009-03-31 14:37:57 -070088 * {@link AppWidgetHost#deleteHost()} is called during database creation.
89 * Use this to recall {@link AppWidgetHost#startListening()} if needed.
90 */
91 static final Uri CONTENT_APPWIDGET_RESET_URI =
92 Uri.parse("content://" + AUTHORITY + "/appWidgetReset");
Daniel Lehmannc3a80402012-04-23 21:35:11 -070093
Michael Jurkaa8c760d2011-04-28 14:59:33 -070094 private DatabaseHelper mOpenHelper;
Winson Chungc763c4e2013-07-19 13:49:06 -070095 private static boolean sJustLoadedFromOldDb;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080096
97 @Override
98 public boolean onCreate() {
Daniel Sandlere4f98912013-06-25 15:13:26 -040099 final Context context = getContext();
100 mOpenHelper = new DatabaseHelper(context);
101 LauncherAppState.setLauncherProvider(this);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800102 return true;
103 }
104
105 @Override
106 public String getType(Uri uri) {
107 SqlArguments args = new SqlArguments(uri, null, null);
108 if (TextUtils.isEmpty(args.where)) {
109 return "vnd.android.cursor.dir/" + args.table;
110 } else {
111 return "vnd.android.cursor.item/" + args.table;
112 }
113 }
114
115 @Override
116 public Cursor query(Uri uri, String[] projection, String selection,
117 String[] selectionArgs, String sortOrder) {
118
119 SqlArguments args = new SqlArguments(uri, selection, selectionArgs);
120 SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
121 qb.setTables(args.table);
122
Romain Guy73b979d2009-06-09 12:57:21 -0700123 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800124 Cursor result = qb.query(db, projection, args.where, args.args, null, null, sortOrder);
125 result.setNotificationUri(getContext().getContentResolver(), uri);
126
127 return result;
128 }
129
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700130 private static long dbInsertAndCheck(DatabaseHelper helper,
131 SQLiteDatabase db, String table, String nullColumnHack, ContentValues values) {
132 if (!values.containsKey(LauncherSettings.Favorites._ID)) {
133 throw new RuntimeException("Error: attempting to add item without specifying an id");
134 }
135 return db.insert(table, nullColumnHack, values);
136 }
137
Adam Cohen228da5a2011-07-27 22:23:47 -0700138 private static void deleteId(SQLiteDatabase db, long id) {
139 Uri uri = LauncherSettings.Favorites.getContentUri(id, false);
140 SqlArguments args = new SqlArguments(uri, null, null);
141 db.delete(args.table, args.where, args.args);
142 }
143
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800144 @Override
145 public Uri insert(Uri uri, ContentValues initialValues) {
146 SqlArguments args = new SqlArguments(uri);
147
148 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700149 final long rowId = dbInsertAndCheck(mOpenHelper, db, args.table, null, initialValues);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800150 if (rowId <= 0) return null;
151
152 uri = ContentUris.withAppendedId(uri, rowId);
153 sendNotify(uri);
154
155 return uri;
156 }
157
158 @Override
159 public int bulkInsert(Uri uri, ContentValues[] values) {
160 SqlArguments args = new SqlArguments(uri);
161
162 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
163 db.beginTransaction();
164 try {
165 int numValues = values.length;
166 for (int i = 0; i < numValues; i++) {
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700167 if (dbInsertAndCheck(mOpenHelper, db, args.table, null, values[i]) < 0) {
168 return 0;
169 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800170 }
171 db.setTransactionSuccessful();
172 } finally {
173 db.endTransaction();
174 }
175
176 sendNotify(uri);
177 return values.length;
178 }
179
180 @Override
181 public int delete(Uri uri, String selection, String[] selectionArgs) {
182 SqlArguments args = new SqlArguments(uri, selection, selectionArgs);
183
184 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
185 int count = db.delete(args.table, args.where, args.args);
186 if (count > 0) sendNotify(uri);
187
188 return count;
189 }
190
191 @Override
192 public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
193 SqlArguments args = new SqlArguments(uri, selection, selectionArgs);
194
195 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
196 int count = db.update(args.table, values, args.where, args.args);
197 if (count > 0) sendNotify(uri);
198
199 return count;
200 }
201
202 private void sendNotify(Uri uri) {
203 String notify = uri.getQueryParameter(PARAMETER_NOTIFY);
204 if (notify == null || "true".equals(notify)) {
205 getContext().getContentResolver().notifyChange(uri, null);
206 }
207 }
208
Adam Cohendcd297f2013-06-18 13:13:40 -0700209 public long generateNewItemId() {
210 return mOpenHelper.generateNewItemId();
211 }
212
Winson Chungc763c4e2013-07-19 13:49:06 -0700213 public void updateMaxItemId(long id) {
214 mOpenHelper.updateMaxItemId(id);
215 }
216
Adam Cohendcd297f2013-06-18 13:13:40 -0700217 public long generateNewScreenId() {
218 return mOpenHelper.generateNewScreenId();
219 }
220
221 // This is only required one time while loading the workspace during the
222 // upgrade path, and should never be called from anywhere else.
223 public void updateMaxScreenId(long maxScreenId) {
224 mOpenHelper.updateMaxScreenId(maxScreenId);
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700225 }
226
Brian Muramatsu5524b492012-10-02 16:55:54 -0700227 /**
Adam Cohene25af792013-06-06 23:08:25 -0700228 * @param Should we load the old db for upgrade? first run only.
229 */
Winson Chungc763c4e2013-07-19 13:49:06 -0700230 synchronized public boolean justLoadedOldDb() {
Daniel Sandlercc8befa2013-06-11 14:45:48 -0400231 String spKey = LauncherAppState.getSharedPreferencesKey();
Adam Cohene25af792013-06-06 23:08:25 -0700232 SharedPreferences sp = getContext().getSharedPreferences(spKey, Context.MODE_PRIVATE);
233
Winson Chungc763c4e2013-07-19 13:49:06 -0700234 boolean loadedOldDb = false || sJustLoadedFromOldDb;
Adam Cohendcd297f2013-06-18 13:13:40 -0700235
Winson Chungc763c4e2013-07-19 13:49:06 -0700236 sJustLoadedFromOldDb = false;
237 if (sp.getBoolean(UPGRADED_FROM_OLD_DATABASE, false)) {
Adam Cohene25af792013-06-06 23:08:25 -0700238
239 SharedPreferences.Editor editor = sp.edit();
Winson Chungc763c4e2013-07-19 13:49:06 -0700240 editor.remove(UPGRADED_FROM_OLD_DATABASE);
Adam Cohene25af792013-06-06 23:08:25 -0700241 editor.commit();
Winson Chungc763c4e2013-07-19 13:49:06 -0700242 loadedOldDb = true;
Adam Cohene25af792013-06-06 23:08:25 -0700243 }
Winson Chungc763c4e2013-07-19 13:49:06 -0700244 return loadedOldDb;
Adam Cohene25af792013-06-06 23:08:25 -0700245 }
246
247 /**
Brian Muramatsu5524b492012-10-02 16:55:54 -0700248 * @param workspaceResId that can be 0 to use default or non-zero for specific resource
249 */
Michael Jurka45355c42012-10-08 13:21:35 +0200250 synchronized public void loadDefaultFavoritesIfNecessary(int origWorkspaceResId) {
Daniel Sandlercc8befa2013-06-11 14:45:48 -0400251 String spKey = LauncherAppState.getSharedPreferencesKey();
Michael Jurkab85f8a42012-04-25 15:48:32 -0700252 SharedPreferences sp = getContext().getSharedPreferences(spKey, Context.MODE_PRIVATE);
Adam Cohene25af792013-06-06 23:08:25 -0700253
Winson Chungc763c4e2013-07-19 13:49:06 -0700254 if (sp.getBoolean(EMPTY_DATABASE_CREATED, false)) {
Michael Jurka45355c42012-10-08 13:21:35 +0200255 int workspaceResId = origWorkspaceResId;
256
Brian Muramatsu5524b492012-10-02 16:55:54 -0700257 // Use default workspace resource if none provided
258 if (workspaceResId == 0) {
Michael Jurka45355c42012-10-08 13:21:35 +0200259 workspaceResId = sp.getInt(DEFAULT_WORKSPACE_RESOURCE_ID, R.xml.default_workspace);
Brian Muramatsu5524b492012-10-02 16:55:54 -0700260 }
261
Michael Jurkab85f8a42012-04-25 15:48:32 -0700262 // Populate favorites table with initial favorites
263 SharedPreferences.Editor editor = sp.edit();
Winson Chungc763c4e2013-07-19 13:49:06 -0700264 editor.remove(EMPTY_DATABASE_CREATED);
Michael Jurka45355c42012-10-08 13:21:35 +0200265 if (origWorkspaceResId != 0) {
266 editor.putInt(DEFAULT_WORKSPACE_RESOURCE_ID, origWorkspaceResId);
267 }
Adam Cohene25af792013-06-06 23:08:25 -0700268
Brian Muramatsu5524b492012-10-02 16:55:54 -0700269 mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(), workspaceResId);
Winson Chungc763c4e2013-07-19 13:49:06 -0700270 mOpenHelper.setFlagJustLoadedOldDb();
Michael Jurkab85f8a42012-04-25 15:48:32 -0700271 editor.commit();
272 }
273 }
274
Winson Chungc763c4e2013-07-19 13:49:06 -0700275 private static interface ContentValuesCallback {
276 public void onRow(ContentValues values);
277 }
278
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800279 private static class DatabaseHelper extends SQLiteOpenHelper {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800280 private static final String TAG_FAVORITES = "favorites";
281 private static final String TAG_FAVORITE = "favorite";
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700282 private static final String TAG_CLOCK = "clock";
283 private static final String TAG_SEARCH = "search";
Mike Cleronb87bd162009-10-30 16:36:56 -0700284 private static final String TAG_APPWIDGET = "appwidget";
285 private static final String TAG_SHORTCUT = "shortcut";
Adam Cohen228da5a2011-07-27 22:23:47 -0700286 private static final String TAG_FOLDER = "folder";
Winson Chungb3302ae2012-05-01 10:19:14 -0700287 private static final String TAG_EXTRA = "extra";
Winson Chung3d503fb2011-07-13 17:25:49 -0700288
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800289 private final Context mContext;
The Android Open Source Project7376fae2009-03-11 12:11:58 -0700290 private final AppWidgetHost mAppWidgetHost;
Adam Cohendcd297f2013-06-18 13:13:40 -0700291 private long mMaxItemId = -1;
292 private long mMaxScreenId = -1;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800293
294 DatabaseHelper(Context context) {
295 super(context, DATABASE_NAME, null, DATABASE_VERSION);
296 mContext = context;
The Android Open Source Project7376fae2009-03-11 12:11:58 -0700297 mAppWidgetHost = new AppWidgetHost(context, Launcher.APPWIDGET_HOST_ID);
Winson Chung3d503fb2011-07-13 17:25:49 -0700298
299 // In the case where neither onCreate nor onUpgrade gets called, we read the maxId from
300 // the DB here
Adam Cohendcd297f2013-06-18 13:13:40 -0700301 if (mMaxItemId == -1) {
302 mMaxItemId = initializeMaxItemId(getWritableDatabase());
303 }
304 if (mMaxScreenId == -1) {
305 mMaxScreenId = initializeMaxScreenId(getWritableDatabase());
Winson Chung3d503fb2011-07-13 17:25:49 -0700306 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800307 }
308
Jeffrey Sharkey2bbcae12009-03-31 14:37:57 -0700309 /**
310 * Send notification that we've deleted the {@link AppWidgetHost},
311 * probably as part of the initial database creation. The receiver may
312 * want to re-call {@link AppWidgetHost#startListening()} to ensure
313 * callbacks are correctly set.
314 */
315 private void sendAppWidgetResetNotify() {
316 final ContentResolver resolver = mContext.getContentResolver();
317 resolver.notifyChange(CONTENT_APPWIDGET_RESET_URI, null);
318 }
319
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800320 @Override
321 public void onCreate(SQLiteDatabase db) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800322 if (LOGD) Log.d(TAG, "creating new launcher database");
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700323
Adam Cohendcd297f2013-06-18 13:13:40 -0700324 mMaxItemId = 1;
325 mMaxScreenId = 0;
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700326
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800327 db.execSQL("CREATE TABLE favorites (" +
328 "_id INTEGER PRIMARY KEY," +
329 "title TEXT," +
330 "intent TEXT," +
331 "container INTEGER," +
332 "screen INTEGER," +
333 "cellX INTEGER," +
334 "cellY INTEGER," +
335 "spanX INTEGER," +
336 "spanY INTEGER," +
337 "itemType INTEGER," +
The Android Open Source Projectca9475f2009-03-13 13:04:24 -0700338 "appWidgetId INTEGER NOT NULL DEFAULT -1," +
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800339 "isShortcut INTEGER," +
340 "iconType INTEGER," +
341 "iconPackage TEXT," +
342 "iconResource TEXT," +
343 "icon BLOB," +
344 "uri TEXT," +
345 "displayMode INTEGER" +
346 ");");
Adam Cohendcd297f2013-06-18 13:13:40 -0700347 addWorkspacesTable(db);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800348
The Android Open Source Project7376fae2009-03-11 12:11:58 -0700349 // Database was just created, so wipe any previous widgets
350 if (mAppWidgetHost != null) {
351 mAppWidgetHost.deleteHost();
Jeffrey Sharkey2bbcae12009-03-31 14:37:57 -0700352 sendAppWidgetResetNotify();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800353 }
Winson Chung3d503fb2011-07-13 17:25:49 -0700354
Winson Chungc763c4e2013-07-19 13:49:06 -0700355 // Try converting the old database
356 ContentValuesCallback permuteScreensCb = new ContentValuesCallback() {
357 public void onRow(ContentValues values) {
358 int container = values.getAsInteger(LauncherSettings.Favorites.CONTAINER);
359 if (container == Favorites.CONTAINER_DESKTOP) {
360 int screen = values.getAsInteger(LauncherSettings.Favorites.SCREEN);
361 screen = (int) upgradeLauncherDb_permuteScreens(screen);
362 values.put(LauncherSettings.Favorites.SCREEN, screen);
363 }
364 }
365 };
366 Uri uri = Uri.parse("content://" + Settings.AUTHORITY +
367 "/old_favorites?notify=true");
368 if (!convertDatabase(db, uri, permuteScreensCb, true)) {
369 // Try and upgrade from the Launcher2 db
370 uri = LauncherSettings.Favorites.OLD_CONTENT_URI;
371 if (!convertDatabase(db, uri, permuteScreensCb, false)) {
372 // If we fail, then set a flag to load the default workspace
373 setFlagEmptyDbCreated();
374 return;
375 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800376 }
Winson Chungc763c4e2013-07-19 13:49:06 -0700377 // Right now, in non-default workspace cases, we want to run the final
378 // upgrade code (ie. to fix workspace screen indices -> ids, etc.), so
379 // set that flag too.
380 setFlagJustLoadedOldDb();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800381 }
382
Adam Cohendcd297f2013-06-18 13:13:40 -0700383 private void addWorkspacesTable(SQLiteDatabase db) {
384 db.execSQL("CREATE TABLE " + TABLE_WORKSPACE_SCREENS + " (" +
385 LauncherSettings.WorkspaceScreens._ID + " INTEGER," +
386 LauncherSettings.WorkspaceScreens.SCREEN_RANK + " INTEGER" +
387 ");");
388 }
389
Winson Chungc763c4e2013-07-19 13:49:06 -0700390 private void setFlagJustLoadedOldDb() {
Daniel Sandlercc8befa2013-06-11 14:45:48 -0400391 String spKey = LauncherAppState.getSharedPreferencesKey();
Michael Jurkab85f8a42012-04-25 15:48:32 -0700392 SharedPreferences sp = mContext.getSharedPreferences(spKey, Context.MODE_PRIVATE);
393 SharedPreferences.Editor editor = sp.edit();
Winson Chungc763c4e2013-07-19 13:49:06 -0700394 editor.putBoolean(UPGRADED_FROM_OLD_DATABASE, true);
395 editor.putBoolean(EMPTY_DATABASE_CREATED, false);
Michael Jurkab85f8a42012-04-25 15:48:32 -0700396 editor.commit();
397 }
398
Winson Chungc763c4e2013-07-19 13:49:06 -0700399 private void setFlagEmptyDbCreated() {
400 String spKey = LauncherAppState.getSharedPreferencesKey();
401 SharedPreferences sp = mContext.getSharedPreferences(spKey, Context.MODE_PRIVATE);
402 SharedPreferences.Editor editor = sp.edit();
403 editor.putBoolean(EMPTY_DATABASE_CREATED, true);
404 editor.putBoolean(UPGRADED_FROM_OLD_DATABASE, false);
405 editor.commit();
406 }
407
408 // We rearrange the screens from the old launcher
409 // 12345 -> 34512
410 private long upgradeLauncherDb_permuteScreens(long screen) {
411 if (screen >= 2) {
412 return screen - 2;
413 } else {
414 return screen + 3;
415 }
416 }
417
418 private boolean convertDatabase(SQLiteDatabase db, Uri uri,
419 ContentValuesCallback cb, boolean deleteRows) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800420 if (LOGD) Log.d(TAG, "converting database from an older format, but not onUpgrade");
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800421 boolean converted = false;
422
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800423 final ContentResolver resolver = mContext.getContentResolver();
424 Cursor cursor = null;
425
426 try {
427 cursor = resolver.query(uri, null, null, null, null);
428 } catch (Exception e) {
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700429 // Ignore
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800430 }
431
432 // We already have a favorites database in the old provider
Winson Chungc763c4e2013-07-19 13:49:06 -0700433 if (cursor != null) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800434 try {
Winson Chungc763c4e2013-07-19 13:49:06 -0700435 if (cursor.getCount() > 0) {
436 converted = copyFromCursor(db, cursor, cb) > 0;
437 if (converted && deleteRows) {
438 resolver.delete(uri, null, null);
439 }
440 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800441 } finally {
442 cursor.close();
443 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800444 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700445
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800446 if (converted) {
The Android Open Source Project7376fae2009-03-11 12:11:58 -0700447 // Convert widgets from this import into widgets
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800448 if (LOGD) Log.d(TAG, "converted and now triggering widget upgrade");
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800449 convertWidgets(db);
Winson Chungc763c4e2013-07-19 13:49:06 -0700450
451 // Update max item id
452 mMaxItemId = initializeMaxItemId(db);
453 if (LOGD) Log.d(TAG, "mMaxItemId: " + mMaxItemId);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800454 }
455
456 return converted;
457 }
458
Winson Chungc763c4e2013-07-19 13:49:06 -0700459 private int copyFromCursor(SQLiteDatabase db, Cursor c, ContentValuesCallback cb) {
Romain Guy73b979d2009-06-09 12:57:21 -0700460 final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800461 final int intentIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.INTENT);
462 final int titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE);
463 final int iconTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_TYPE);
464 final int iconIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON);
465 final int iconPackageIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_PACKAGE);
466 final int iconResourceIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_RESOURCE);
467 final int containerIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER);
468 final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE);
469 final int screenIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN);
470 final int cellXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX);
471 final int cellYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY);
472 final int uriIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.URI);
473 final int displayModeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.DISPLAY_MODE);
474
475 ContentValues[] rows = new ContentValues[c.getCount()];
476 int i = 0;
477 while (c.moveToNext()) {
478 ContentValues values = new ContentValues(c.getColumnCount());
Romain Guy73b979d2009-06-09 12:57:21 -0700479 values.put(LauncherSettings.Favorites._ID, c.getLong(idIndex));
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800480 values.put(LauncherSettings.Favorites.INTENT, c.getString(intentIndex));
481 values.put(LauncherSettings.Favorites.TITLE, c.getString(titleIndex));
482 values.put(LauncherSettings.Favorites.ICON_TYPE, c.getInt(iconTypeIndex));
483 values.put(LauncherSettings.Favorites.ICON, c.getBlob(iconIndex));
484 values.put(LauncherSettings.Favorites.ICON_PACKAGE, c.getString(iconPackageIndex));
485 values.put(LauncherSettings.Favorites.ICON_RESOURCE, c.getString(iconResourceIndex));
486 values.put(LauncherSettings.Favorites.CONTAINER, c.getInt(containerIndex));
487 values.put(LauncherSettings.Favorites.ITEM_TYPE, c.getInt(itemTypeIndex));
The Android Open Source Project7376fae2009-03-11 12:11:58 -0700488 values.put(LauncherSettings.Favorites.APPWIDGET_ID, -1);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800489 values.put(LauncherSettings.Favorites.SCREEN, c.getInt(screenIndex));
490 values.put(LauncherSettings.Favorites.CELLX, c.getInt(cellXIndex));
491 values.put(LauncherSettings.Favorites.CELLY, c.getInt(cellYIndex));
492 values.put(LauncherSettings.Favorites.URI, c.getString(uriIndex));
493 values.put(LauncherSettings.Favorites.DISPLAY_MODE, c.getInt(displayModeIndex));
Winson Chungc763c4e2013-07-19 13:49:06 -0700494 if (cb != null) {
495 cb.onRow(values);
496 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800497 rows[i++] = values;
498 }
499
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800500 int total = 0;
Winson Chungc763c4e2013-07-19 13:49:06 -0700501 if (i > 0) {
502 db.beginTransaction();
503 try {
504 int numValues = rows.length;
505 for (i = 0; i < numValues; i++) {
506 if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, rows[i]) < 0) {
507 return 0;
508 } else {
509 total++;
510 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800511 }
Winson Chungc763c4e2013-07-19 13:49:06 -0700512 db.setTransactionSuccessful();
513 } finally {
514 db.endTransaction();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800515 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800516 }
517
518 return total;
519 }
520
521 @Override
522 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Winson Chungc763c4e2013-07-19 13:49:06 -0700523 if (LOGD) Log.d(TAG, "onUpgrade triggered: " + oldVersion);
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700524
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800525 int version = oldVersion;
The Android Open Source Projectca9475f2009-03-13 13:04:24 -0700526 if (version < 3) {
527 // upgrade 1,2 -> 3 added appWidgetId column
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800528 db.beginTransaction();
529 try {
The Android Open Source Project7376fae2009-03-11 12:11:58 -0700530 // Insert new column for holding appWidgetIds
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800531 db.execSQL("ALTER TABLE favorites " +
The Android Open Source Projectca9475f2009-03-13 13:04:24 -0700532 "ADD COLUMN appWidgetId INTEGER NOT NULL DEFAULT -1;");
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800533 db.setTransactionSuccessful();
The Android Open Source Projectca9475f2009-03-13 13:04:24 -0700534 version = 3;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800535 } catch (SQLException ex) {
536 // Old version remains, which means we wipe old data
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800537 Log.e(TAG, ex.getMessage(), ex);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800538 } finally {
539 db.endTransaction();
540 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700541
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800542 // Convert existing widgets only if table upgrade was successful
The Android Open Source Projectca9475f2009-03-13 13:04:24 -0700543 if (version == 3) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800544 convertWidgets(db);
545 }
546 }
Romain Guy73b979d2009-06-09 12:57:21 -0700547
548 if (version < 4) {
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800549 version = 4;
Romain Guy73b979d2009-06-09 12:57:21 -0700550 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700551
Romain Guy509cd6a2010-03-23 15:10:56 -0700552 // Where's version 5?
553 // - Donut and sholes on 2.0 shipped with version 4 of launcher1.
Daniel Sandler325dc232013-06-05 22:57:57 -0400554 // - Passion shipped on 2.1 with version 6 of launcher3
Romain Guy509cd6a2010-03-23 15:10:56 -0700555 // - Sholes shipped on 2.1r1 (aka Mr. 3) with version 5 of launcher 1
556 // but version 5 on there was the updateContactsShortcuts change
557 // which was version 6 in launcher 2 (first shipped on passion 2.1r1).
558 // The updateContactsShortcuts change is idempotent, so running it twice
559 // is okay so we'll do that when upgrading the devices that shipped with it.
560 if (version < 6) {
Mike Cleron3a2b3f22009-11-05 17:17:42 -0800561 // We went from 3 to 5 screens. Move everything 1 to the right
562 db.beginTransaction();
563 try {
564 db.execSQL("UPDATE favorites SET screen=(screen + 1);");
565 db.setTransactionSuccessful();
Mike Cleron3a2b3f22009-11-05 17:17:42 -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);
Mike Cleron3a2b3f22009-11-05 17:17:42 -0800569 } finally {
570 db.endTransaction();
571 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700572
Romain Guy509cd6a2010-03-23 15:10:56 -0700573 // We added the fast track.
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800574 if (updateContactsShortcuts(db)) {
575 version = 6;
576 }
577 }
Bjorn Bringert7984c942009-12-09 15:38:25 +0000578
579 if (version < 7) {
580 // Version 7 gets rid of the special search widget.
581 convertWidgets(db);
582 version = 7;
583 }
584
Joe Onorato0589f0f2010-02-08 13:44:00 -0800585 if (version < 8) {
586 // Version 8 (froyo) has the icons all normalized. This should
587 // already be the case in practice, but we now rely on it and don't
588 // resample the images each time.
589 normalizeIcons(db);
590 version = 8;
591 }
592
Winson Chung3d503fb2011-07-13 17:25:49 -0700593 if (version < 9) {
594 // The max id is not yet set at this point (onUpgrade is triggered in the ctor
595 // 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 -0700596 if (mMaxItemId == -1) {
597 mMaxItemId = initializeMaxItemId(db);
Winson Chung3d503fb2011-07-13 17:25:49 -0700598 }
599
600 // Add default hotseat icons
Winson Chung6d092682011-11-16 18:43:26 -0800601 loadFavorites(db, R.xml.update_workspace);
Winson Chung3d503fb2011-07-13 17:25:49 -0700602 version = 9;
603 }
604
Daniel Lehmannd02402c2012-05-14 18:30:53 -0700605 // We bumped the version three time during JB, once to update the launch flags, once to
606 // update the override for the default launch animation and once to set the mimetype
607 // to improve startup performance
608 if (version < 12) {
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700609 // Contact shortcuts need a different set of flags to be launched now
610 // The updateContactsShortcuts change is idempotent, so we can keep using it like
611 // back in the Donut days
612 updateContactsShortcuts(db);
Daniel Lehmannd02402c2012-05-14 18:30:53 -0700613 version = 12;
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700614 }
615
Adam Cohendcd297f2013-06-18 13:13:40 -0700616 if (version < 13) {
617 // With the new shrink-wrapped and re-orderable workspaces, it makes sense
618 // to persist workspace screens and their relative order.
619 mMaxScreenId = 0;
620
621 // This will never happen in the wild, but when we switch to using workspace
622 // screen ids, redo the import from old launcher.
Winson Chungc763c4e2013-07-19 13:49:06 -0700623 sJustLoadedFromOldDb = true;
Adam Cohendcd297f2013-06-18 13:13:40 -0700624
625 addWorkspacesTable(db);
626 version = 13;
627 }
628
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800629 if (version != DATABASE_VERSION) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800630 Log.w(TAG, "Destroying all old data.");
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800631 db.execSQL("DROP TABLE IF EXISTS " + TABLE_FAVORITES);
Adam Cohendcd297f2013-06-18 13:13:40 -0700632 db.execSQL("DROP TABLE IF EXISTS " + TABLE_WORKSPACE_SCREENS);
633
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800634 onCreate(db);
635 }
636 }
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800637
638 private boolean updateContactsShortcuts(SQLiteDatabase db) {
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800639 final String selectWhere = buildOrWhereString(Favorites.ITEM_TYPE,
640 new int[] { Favorites.ITEM_TYPE_SHORTCUT });
641
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700642 Cursor c = null;
643 final String actionQuickContact = "com.android.contacts.action.QUICK_CONTACT";
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800644 db.beginTransaction();
645 try {
646 // Select and iterate through each matching widget
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700647 c = db.query(TABLE_FAVORITES,
648 new String[] { Favorites._ID, Favorites.INTENT },
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800649 selectWhere, null, null, null, null);
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700650 if (c == null) return false;
651
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800652 if (LOGD) Log.d(TAG, "found upgrade cursor count=" + c.getCount());
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700653
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800654 final int idIndex = c.getColumnIndex(Favorites._ID);
655 final int intentIndex = c.getColumnIndex(Favorites.INTENT);
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700656
657 while (c.moveToNext()) {
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800658 long favoriteId = c.getLong(idIndex);
659 final String intentUri = c.getString(intentIndex);
660 if (intentUri != null) {
661 try {
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700662 final Intent intent = Intent.parseUri(intentUri, 0);
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800663 android.util.Log.d("Home", intent.toString());
664 final Uri uri = intent.getData();
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700665 if (uri != null) {
666 final String data = uri.toString();
667 if ((Intent.ACTION_VIEW.equals(intent.getAction()) ||
668 actionQuickContact.equals(intent.getAction())) &&
669 (data.startsWith("content://contacts/people/") ||
670 data.startsWith("content://com.android.contacts/" +
671 "contacts/lookup/"))) {
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800672
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700673 final Intent newIntent = new Intent(actionQuickContact);
674 // When starting from the launcher, start in a new, cleared task
675 // CLEAR_WHEN_TASK_RESET cannot reset the root of a task, so we
676 // clear the whole thing preemptively here since
677 // QuickContactActivity will finish itself when launching other
678 // detail activities.
679 newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
680 Intent.FLAG_ACTIVITY_CLEAR_TASK);
Winson Chung2672ff92012-05-04 16:22:30 -0700681 newIntent.putExtra(
682 Launcher.INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION, true);
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700683 newIntent.setData(uri);
Daniel Lehmannd02402c2012-05-14 18:30:53 -0700684 // Determine the type and also put that in the shortcut
685 // (that can speed up launch a bit)
686 newIntent.setDataAndType(uri, newIntent.resolveType(mContext));
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800687
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700688 final ContentValues values = new ContentValues();
689 values.put(LauncherSettings.Favorites.INTENT,
690 newIntent.toUri(0));
691
692 String updateWhere = Favorites._ID + "=" + favoriteId;
693 db.update(TABLE_FAVORITES, values, updateWhere, null);
694 }
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800695 }
696 } catch (RuntimeException ex) {
697 Log.e(TAG, "Problem upgrading shortcut", ex);
698 } catch (URISyntaxException e) {
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700699 Log.e(TAG, "Problem upgrading shortcut", e);
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800700 }
701 }
702 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700703
Romain Guy7eb9e5e2009-12-02 20:10:07 -0800704 db.setTransactionSuccessful();
705 } catch (SQLException ex) {
706 Log.w(TAG, "Problem while upgrading contacts", ex);
707 return false;
708 } finally {
709 db.endTransaction();
710 if (c != null) {
711 c.close();
712 }
713 }
714
715 return true;
716 }
717
Joe Onorato0589f0f2010-02-08 13:44:00 -0800718 private void normalizeIcons(SQLiteDatabase db) {
719 Log.d(TAG, "normalizing icons");
720
Joe Onorato346e1292010-02-18 10:34:24 -0500721 db.beginTransaction();
Joe Onorato0589f0f2010-02-08 13:44:00 -0800722 Cursor c = null;
Joe Onorato9690b392010-03-23 17:34:37 -0400723 SQLiteStatement update = null;
Joe Onorato0589f0f2010-02-08 13:44:00 -0800724 try {
725 boolean logged = false;
Joe Onorato9690b392010-03-23 17:34:37 -0400726 update = db.compileStatement("UPDATE favorites "
Jeff Hamiltoneaf77d62010-02-13 00:08:17 -0600727 + "SET icon=? WHERE _id=?");
Joe Onorato0589f0f2010-02-08 13:44:00 -0800728
729 c = db.rawQuery("SELECT _id, icon FROM favorites WHERE iconType=" +
730 Favorites.ICON_TYPE_BITMAP, null);
731
732 final int idIndex = c.getColumnIndexOrThrow(Favorites._ID);
733 final int iconIndex = c.getColumnIndexOrThrow(Favorites.ICON);
734
735 while (c.moveToNext()) {
736 long id = c.getLong(idIndex);
737 byte[] data = c.getBlob(iconIndex);
738 try {
739 Bitmap bitmap = Utilities.resampleIconBitmap(
740 BitmapFactory.decodeByteArray(data, 0, data.length),
741 mContext);
742 if (bitmap != null) {
743 update.bindLong(1, id);
744 data = ItemInfo.flattenBitmap(bitmap);
745 if (data != null) {
746 update.bindBlob(2, data);
747 update.execute();
748 }
749 bitmap.recycle();
Joe Onorato0589f0f2010-02-08 13:44:00 -0800750 }
751 } catch (Exception e) {
752 if (!logged) {
753 Log.e(TAG, "Failed normalizing icon " + id, e);
754 } else {
755 Log.e(TAG, "Also failed normalizing icon " + id);
756 }
757 logged = true;
758 }
759 }
Bjorn Bringert3a928e42010-02-19 11:15:40 +0000760 db.setTransactionSuccessful();
Joe Onorato0589f0f2010-02-08 13:44:00 -0800761 } catch (SQLException ex) {
762 Log.w(TAG, "Problem while allocating appWidgetIds for existing widgets", ex);
763 } finally {
764 db.endTransaction();
Joe Onorato9690b392010-03-23 17:34:37 -0400765 if (update != null) {
766 update.close();
767 }
Joe Onorato0589f0f2010-02-08 13:44:00 -0800768 if (c != null) {
769 c.close();
770 }
771 }
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700772 }
773
774 // Generates a new ID to use for an object in your database. This method should be only
775 // called from the main UI thread. As an exception, we do call it when we call the
776 // constructor from the worker thread; however, this doesn't extend until after the
777 // constructor is called, and we only pass a reference to LauncherProvider to LauncherApp
778 // after that point
Adam Cohendcd297f2013-06-18 13:13:40 -0700779 public long generateNewItemId() {
780 if (mMaxItemId < 0) {
781 throw new RuntimeException("Error: max item id was not initialized");
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700782 }
Adam Cohendcd297f2013-06-18 13:13:40 -0700783 mMaxItemId += 1;
784 return mMaxItemId;
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700785 }
786
Winson Chungc763c4e2013-07-19 13:49:06 -0700787 public void updateMaxItemId(long id) {
788 mMaxItemId = id + 1;
789 }
790
Adam Cohendcd297f2013-06-18 13:13:40 -0700791 private long initializeMaxItemId(SQLiteDatabase db) {
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700792 Cursor c = db.rawQuery("SELECT MAX(_id) FROM favorites", null);
793
794 // get the result
795 final int maxIdIndex = 0;
796 long id = -1;
797 if (c != null && c.moveToNext()) {
798 id = c.getLong(maxIdIndex);
799 }
Michael Jurka5130e402011-10-13 04:55:35 -0700800 if (c != null) {
801 c.close();
802 }
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700803
804 if (id == -1) {
Adam Cohendcd297f2013-06-18 13:13:40 -0700805 throw new RuntimeException("Error: could not query max item id");
806 }
807
808 return id;
809 }
810
811 // Generates a new ID to use for an workspace screen in your database. This method
812 // should be only called from the main UI thread. As an exception, we do call it when we
813 // call the constructor from the worker thread; however, this doesn't extend until after the
814 // constructor is called, and we only pass a reference to LauncherProvider to LauncherApp
815 // after that point
816 public long generateNewScreenId() {
817 if (mMaxScreenId < 0) {
818 throw new RuntimeException("Error: max screen id was not initialized");
819 }
820 mMaxScreenId += 1;
821 return mMaxScreenId;
822 }
823
824 public void updateMaxScreenId(long maxScreenId) {
825 mMaxScreenId = maxScreenId;
826 }
827
828 private long initializeMaxScreenId(SQLiteDatabase db) {
829 Cursor c = db.rawQuery("SELECT MAX(" + LauncherSettings.WorkspaceScreens._ID + ") FROM " + TABLE_WORKSPACE_SCREENS, null);
830
831 // get the result
832 final int maxIdIndex = 0;
833 long id = -1;
834 if (c != null && c.moveToNext()) {
835 id = c.getLong(maxIdIndex);
836 }
837 if (c != null) {
838 c.close();
839 }
840
841 if (id == -1) {
842 throw new RuntimeException("Error: could not query max screen id");
Michael Jurkaa8c760d2011-04-28 14:59:33 -0700843 }
844
845 return id;
Joe Onorato0589f0f2010-02-08 13:44:00 -0800846 }
847
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800848 /**
The Android Open Source Project7376fae2009-03-11 12:11:58 -0700849 * Upgrade existing clock and photo frame widgets into their new widget
Bjorn Bringert93c45762009-12-16 13:19:47 +0000850 * equivalents.
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800851 */
852 private void convertWidgets(SQLiteDatabase db) {
Bjorn Bringert34251342009-12-15 13:33:11 +0000853 final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800854 final int[] bindSources = new int[] {
855 Favorites.ITEM_TYPE_WIDGET_CLOCK,
856 Favorites.ITEM_TYPE_WIDGET_PHOTO_FRAME,
Bjorn Bringert7984c942009-12-09 15:38:25 +0000857 Favorites.ITEM_TYPE_WIDGET_SEARCH,
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800858 };
Bjorn Bringert7984c942009-12-09 15:38:25 +0000859
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800860 final String selectWhere = buildOrWhereString(Favorites.ITEM_TYPE, bindSources);
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700861
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800862 Cursor c = null;
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700863
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800864 db.beginTransaction();
865 try {
866 // Select and iterate through each matching widget
Bjorn Bringert7984c942009-12-09 15:38:25 +0000867 c = db.query(TABLE_FAVORITES, new String[] { Favorites._ID, Favorites.ITEM_TYPE },
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800868 selectWhere, null, null, null, null);
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700869
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800870 if (LOGD) Log.d(TAG, "found upgrade cursor count=" + c.getCount());
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700871
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800872 final ContentValues values = new ContentValues();
873 while (c != null && c.moveToNext()) {
874 long favoriteId = c.getLong(0);
Bjorn Bringert7984c942009-12-09 15:38:25 +0000875 int favoriteType = c.getInt(1);
876
The Android Open Source Project7376fae2009-03-11 12:11:58 -0700877 // Allocate and update database with new appWidgetId
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800878 try {
The Android Open Source Project7376fae2009-03-11 12:11:58 -0700879 int appWidgetId = mAppWidgetHost.allocateAppWidgetId();
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700880
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800881 if (LOGD) {
882 Log.d(TAG, "allocated appWidgetId=" + appWidgetId
883 + " for favoriteId=" + favoriteId);
884 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800885 values.clear();
Bjorn Bringert7984c942009-12-09 15:38:25 +0000886 values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_APPWIDGET);
887 values.put(Favorites.APPWIDGET_ID, appWidgetId);
888
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800889 // Original widgets might not have valid spans when upgrading
Bjorn Bringert7984c942009-12-09 15:38:25 +0000890 if (favoriteType == Favorites.ITEM_TYPE_WIDGET_SEARCH) {
891 values.put(LauncherSettings.Favorites.SPANX, 4);
892 values.put(LauncherSettings.Favorites.SPANY, 1);
893 } else {
894 values.put(LauncherSettings.Favorites.SPANX, 2);
895 values.put(LauncherSettings.Favorites.SPANY, 2);
896 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800897
898 String updateWhere = Favorites._ID + "=" + favoriteId;
899 db.update(TABLE_FAVORITES, values, updateWhere, null);
Bjorn Bringert34251342009-12-15 13:33:11 +0000900
Bjorn Bringert34251342009-12-15 13:33:11 +0000901 if (favoriteType == Favorites.ITEM_TYPE_WIDGET_CLOCK) {
Michael Jurka8b805b12012-04-18 14:23:14 -0700902 // TODO: check return value
903 appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId,
Bjorn Bringert34251342009-12-15 13:33:11 +0000904 new ComponentName("com.android.alarmclock",
905 "com.android.alarmclock.AnalogAppWidgetProvider"));
906 } else if (favoriteType == Favorites.ITEM_TYPE_WIDGET_PHOTO_FRAME) {
Michael Jurka8b805b12012-04-18 14:23:14 -0700907 // TODO: check return value
908 appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId,
Bjorn Bringert34251342009-12-15 13:33:11 +0000909 new ComponentName("com.android.camera",
910 "com.android.camera.PhotoAppWidgetProvider"));
911 } else if (favoriteType == Favorites.ITEM_TYPE_WIDGET_SEARCH) {
Michael Jurka8b805b12012-04-18 14:23:14 -0700912 // TODO: check return value
913 appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId,
Bjorn Bringertcd8fec02010-01-14 13:26:43 +0000914 getSearchWidgetProvider());
Bjorn Bringert34251342009-12-15 13:33:11 +0000915 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800916 } catch (RuntimeException ex) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800917 Log.e(TAG, "Problem allocating appWidgetId", ex);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800918 }
919 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -0700920
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800921 db.setTransactionSuccessful();
922 } catch (SQLException ex) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -0800923 Log.w(TAG, "Problem while allocating appWidgetIds for existing widgets", ex);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800924 } finally {
925 db.endTransaction();
926 if (c != null) {
927 c.close();
928 }
929 }
Winson Chungc763c4e2013-07-19 13:49:06 -0700930
931 // Update max item id
932 mMaxItemId = initializeMaxItemId(db);
933 if (LOGD) Log.d(TAG, "mMaxItemId: " + mMaxItemId);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800934 }
935
Michael Jurka8b805b12012-04-18 14:23:14 -0700936 private static final void beginDocument(XmlPullParser parser, String firstElementName)
937 throws XmlPullParserException, IOException {
938 int type;
Michael Jurka9bc8eba2012-05-21 20:36:44 -0700939 while ((type = parser.next()) != XmlPullParser.START_TAG
940 && type != XmlPullParser.END_DOCUMENT) {
Michael Jurka8b805b12012-04-18 14:23:14 -0700941 ;
942 }
943
Michael Jurka9bc8eba2012-05-21 20:36:44 -0700944 if (type != XmlPullParser.START_TAG) {
Michael Jurka8b805b12012-04-18 14:23:14 -0700945 throw new XmlPullParserException("No start tag found");
946 }
947
948 if (!parser.getName().equals(firstElementName)) {
949 throw new XmlPullParserException("Unexpected start tag: found " + parser.getName() +
950 ", expected " + firstElementName);
951 }
952 }
953
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800954 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800955 * Loads the default set of favorite packages from an xml file.
956 *
957 * @param db The database to write the values into
Winson Chung3d503fb2011-07-13 17:25:49 -0700958 * @param filterContainerId The specific container id of items to load
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800959 */
Winson Chung6d092682011-11-16 18:43:26 -0800960 private int loadFavorites(SQLiteDatabase db, int workspaceResourceId) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800961 Intent intent = new Intent(Intent.ACTION_MAIN, null);
962 intent.addCategory(Intent.CATEGORY_LAUNCHER);
963 ContentValues values = new ContentValues();
964
965 PackageManager packageManager = mContext.getPackageManager();
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800966 int allAppsButtonRank =
967 mContext.getResources().getInteger(R.integer.hotseat_all_apps_index);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800968 int i = 0;
969 try {
Winson Chung6d092682011-11-16 18:43:26 -0800970 XmlResourceParser parser = mContext.getResources().getXml(workspaceResourceId);
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700971 AttributeSet attrs = Xml.asAttributeSet(parser);
Michael Jurka8b805b12012-04-18 14:23:14 -0700972 beginDocument(parser, TAG_FAVORITES);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800973
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700974 final int depth = parser.getDepth();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800975
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700976 int type;
977 while (((type = parser.next()) != XmlPullParser.END_TAG ||
978 parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
979
980 if (type != XmlPullParser.START_TAG) {
981 continue;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800982 }
983
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700984 boolean added = false;
985 final String name = parser.getName();
986
987 TypedArray a = mContext.obtainStyledAttributes(attrs, R.styleable.Favorite);
988
Winson Chung3d503fb2011-07-13 17:25:49 -0700989 long container = LauncherSettings.Favorites.CONTAINER_DESKTOP;
990 if (a.hasValue(R.styleable.Favorite_container)) {
991 container = Long.valueOf(a.getString(R.styleable.Favorite_container));
992 }
The Android Open Source Projectf96811c2009-03-18 17:39:48 -0700993
Winson Chung6d092682011-11-16 18:43:26 -0800994 String screen = a.getString(R.styleable.Favorite_screen);
995 String x = a.getString(R.styleable.Favorite_x);
996 String y = a.getString(R.styleable.Favorite_y);
997
998 // If we are adding to the hotseat, the screen is used as the position in the
999 // hotseat. This screen can't be at position 0 because AllApps is in the
1000 // zeroth position.
Andrew Flynn0dca1ec2012-02-29 13:33:22 -08001001 if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT
1002 && Integer.valueOf(screen) == allAppsButtonRank) {
Winson Chung6d092682011-11-16 18:43:26 -08001003 throw new RuntimeException("Invalid screen position for hotseat item");
1004 }
1005
1006 values.clear();
1007 values.put(LauncherSettings.Favorites.CONTAINER, container);
1008 values.put(LauncherSettings.Favorites.SCREEN, screen);
1009 values.put(LauncherSettings.Favorites.CELLX, x);
1010 values.put(LauncherSettings.Favorites.CELLY, y);
1011
1012 if (TAG_FAVORITE.equals(name)) {
1013 long id = addAppShortcut(db, values, a, packageManager, intent);
1014 added = id >= 0;
1015 } else if (TAG_SEARCH.equals(name)) {
1016 added = addSearchWidget(db, values);
1017 } else if (TAG_CLOCK.equals(name)) {
1018 added = addClockWidget(db, values);
1019 } else if (TAG_APPWIDGET.equals(name)) {
Winson Chungb3302ae2012-05-01 10:19:14 -07001020 added = addAppWidget(parser, attrs, type, db, values, a, packageManager);
Winson Chung6d092682011-11-16 18:43:26 -08001021 } else if (TAG_SHORTCUT.equals(name)) {
1022 long id = addUriShortcut(db, values, a);
1023 added = id >= 0;
1024 } else if (TAG_FOLDER.equals(name)) {
1025 String title;
1026 int titleResId = a.getResourceId(R.styleable.Favorite_title, -1);
1027 if (titleResId != -1) {
1028 title = mContext.getResources().getString(titleResId);
1029 } else {
1030 title = mContext.getResources().getString(R.string.folder_name);
Winson Chung3d503fb2011-07-13 17:25:49 -07001031 }
Winson Chung6d092682011-11-16 18:43:26 -08001032 values.put(LauncherSettings.Favorites.TITLE, title);
1033 long folderId = addFolder(db, values);
1034 added = folderId >= 0;
Winson Chung3d503fb2011-07-13 17:25:49 -07001035
Winson Chung6d092682011-11-16 18:43:26 -08001036 ArrayList<Long> folderItems = new ArrayList<Long>();
Winson Chung3d503fb2011-07-13 17:25:49 -07001037
Winson Chung6d092682011-11-16 18:43:26 -08001038 int folderDepth = parser.getDepth();
1039 while ((type = parser.next()) != XmlPullParser.END_TAG ||
1040 parser.getDepth() > folderDepth) {
1041 if (type != XmlPullParser.START_TAG) {
1042 continue;
1043 }
1044 final String folder_item_name = parser.getName();
1045
1046 TypedArray ar = mContext.obtainStyledAttributes(attrs,
1047 R.styleable.Favorite);
1048 values.clear();
1049 values.put(LauncherSettings.Favorites.CONTAINER, folderId);
1050
1051 if (TAG_FAVORITE.equals(folder_item_name) && folderId >= 0) {
1052 long id =
1053 addAppShortcut(db, values, ar, packageManager, intent);
1054 if (id >= 0) {
1055 folderItems.add(id);
1056 }
1057 } else if (TAG_SHORTCUT.equals(folder_item_name) && folderId >= 0) {
1058 long id = addUriShortcut(db, values, ar);
1059 if (id >= 0) {
1060 folderItems.add(id);
1061 }
Adam Cohen228da5a2011-07-27 22:23:47 -07001062 } else {
Winson Chung6d092682011-11-16 18:43:26 -08001063 throw new RuntimeException("Folders can " +
1064 "contain only shortcuts");
Adam Cohen228da5a2011-07-27 22:23:47 -07001065 }
Winson Chung6d092682011-11-16 18:43:26 -08001066 ar.recycle();
1067 }
1068 // We can only have folders with >= 2 items, so we need to remove the
1069 // folder and clean up if less than 2 items were included, or some
1070 // failed to add, and less than 2 were actually added
1071 if (folderItems.size() < 2 && folderId >= 0) {
1072 // We just delete the folder and any items that made it
1073 deleteId(db, folderId);
1074 if (folderItems.size() > 0) {
1075 deleteId(db, folderItems.get(0));
Adam Cohen228da5a2011-07-27 22:23:47 -07001076 }
Winson Chung6d092682011-11-16 18:43:26 -08001077 added = false;
Winson Chung3d503fb2011-07-13 17:25:49 -07001078 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001079 }
The Android Open Source Projectf96811c2009-03-18 17:39:48 -07001080 if (added) i++;
The Android Open Source Projectf96811c2009-03-18 17:39:48 -07001081 a.recycle();
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001082 }
1083 } catch (XmlPullParserException e) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -08001084 Log.w(TAG, "Got exception parsing favorites.", e);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001085 } catch (IOException e) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -08001086 Log.w(TAG, "Got exception parsing favorites.", e);
Winson Chung3d503fb2011-07-13 17:25:49 -07001087 } catch (RuntimeException e) {
1088 Log.w(TAG, "Got exception parsing favorites.", e);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001089 }
The Android Open Source Projectf96811c2009-03-18 17:39:48 -07001090
Winson Chungc763c4e2013-07-19 13:49:06 -07001091 // Update the max item id after we have loaded the database
1092 if (mMaxItemId == -1) {
1093 mMaxItemId = initializeMaxItemId(db);
1094 }
1095
The Android Open Source Projectf96811c2009-03-18 17:39:48 -07001096 return i;
1097 }
1098
Adam Cohen228da5a2011-07-27 22:23:47 -07001099 private long addAppShortcut(SQLiteDatabase db, ContentValues values, TypedArray a,
The Android Open Source Projectf96811c2009-03-18 17:39:48 -07001100 PackageManager packageManager, Intent intent) {
Adam Cohen228da5a2011-07-27 22:23:47 -07001101 long id = -1;
The Android Open Source Projectf96811c2009-03-18 17:39:48 -07001102 ActivityInfo info;
1103 String packageName = a.getString(R.styleable.Favorite_packageName);
1104 String className = a.getString(R.styleable.Favorite_className);
1105 try {
Romain Guy693599f2010-03-23 10:58:18 -07001106 ComponentName cn;
1107 try {
1108 cn = new ComponentName(packageName, className);
1109 info = packageManager.getActivityInfo(cn, 0);
1110 } catch (PackageManager.NameNotFoundException nnfe) {
1111 String[] packages = packageManager.currentToCanonicalPackageNames(
1112 new String[] { packageName });
1113 cn = new ComponentName(packages[0], className);
1114 info = packageManager.getActivityInfo(cn, 0);
1115 }
Adam Cohendcd297f2013-06-18 13:13:40 -07001116 id = generateNewItemId();
The Android Open Source Projectf96811c2009-03-18 17:39:48 -07001117 intent.setComponent(cn);
Romain Guy693599f2010-03-23 10:58:18 -07001118 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
1119 Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
Romain Guy1ce1a242009-06-23 17:34:54 -07001120 values.put(Favorites.INTENT, intent.toUri(0));
The Android Open Source Projectf96811c2009-03-18 17:39:48 -07001121 values.put(Favorites.TITLE, info.loadLabel(packageManager).toString());
1122 values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_APPLICATION);
1123 values.put(Favorites.SPANX, 1);
1124 values.put(Favorites.SPANY, 1);
Adam Cohendcd297f2013-06-18 13:13:40 -07001125 values.put(Favorites._ID, generateNewItemId());
Adam Cohen228da5a2011-07-27 22:23:47 -07001126 if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values) < 0) {
1127 return -1;
1128 }
The Android Open Source Projectf96811c2009-03-18 17:39:48 -07001129 } catch (PackageManager.NameNotFoundException e) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -08001130 Log.w(TAG, "Unable to add favorite: " + packageName +
The Android Open Source Projectf96811c2009-03-18 17:39:48 -07001131 "/" + className, e);
The Android Open Source Projectf96811c2009-03-18 17:39:48 -07001132 }
Adam Cohen228da5a2011-07-27 22:23:47 -07001133 return id;
1134 }
1135
1136 private long addFolder(SQLiteDatabase db, ContentValues values) {
1137 values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_FOLDER);
1138 values.put(Favorites.SPANX, 1);
1139 values.put(Favorites.SPANY, 1);
Adam Cohendcd297f2013-06-18 13:13:40 -07001140 long id = generateNewItemId();
Adam Cohen228da5a2011-07-27 22:23:47 -07001141 values.put(Favorites._ID, id);
1142 if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values) <= 0) {
1143 return -1;
1144 } else {
1145 return id;
1146 }
The Android Open Source Projectf96811c2009-03-18 17:39:48 -07001147 }
1148
Bjorn Bringertcd8fec02010-01-14 13:26:43 +00001149 private ComponentName getSearchWidgetProvider() {
1150 SearchManager searchManager =
1151 (SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE);
1152 ComponentName searchComponent = searchManager.getGlobalSearchActivity();
1153 if (searchComponent == null) return null;
1154 return getProviderInPackage(searchComponent.getPackageName());
1155 }
1156
1157 /**
1158 * Gets an appwidget provider from the given package. If the package contains more than
1159 * one appwidget provider, an arbitrary one is returned.
1160 */
1161 private ComponentName getProviderInPackage(String packageName) {
1162 AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext);
1163 List<AppWidgetProviderInfo> providers = appWidgetManager.getInstalledProviders();
1164 if (providers == null) return null;
1165 final int providerCount = providers.size();
1166 for (int i = 0; i < providerCount; i++) {
1167 ComponentName provider = providers.get(i).provider;
1168 if (provider != null && provider.getPackageName().equals(packageName)) {
1169 return provider;
1170 }
1171 }
1172 return null;
1173 }
1174
The Android Open Source Projectf96811c2009-03-18 17:39:48 -07001175 private boolean addSearchWidget(SQLiteDatabase db, ContentValues values) {
Bjorn Bringertcd8fec02010-01-14 13:26:43 +00001176 ComponentName cn = getSearchWidgetProvider();
Winson Chungb3302ae2012-05-01 10:19:14 -07001177 return addAppWidget(db, values, cn, 4, 1, null);
The Android Open Source Projectf96811c2009-03-18 17:39:48 -07001178 }
1179
1180 private boolean addClockWidget(SQLiteDatabase db, ContentValues values) {
Bjorn Bringert34251342009-12-15 13:33:11 +00001181 ComponentName cn = new ComponentName("com.android.alarmclock",
1182 "com.android.alarmclock.AnalogAppWidgetProvider");
Winson Chungb3302ae2012-05-01 10:19:14 -07001183 return addAppWidget(db, values, cn, 2, 2, null);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001184 }
Adam Cohen228da5a2011-07-27 22:23:47 -07001185
Winson Chungb3302ae2012-05-01 10:19:14 -07001186 private boolean addAppWidget(XmlResourceParser parser, AttributeSet attrs, int type,
1187 SQLiteDatabase db, ContentValues values, TypedArray a,
1188 PackageManager packageManager) throws XmlPullParserException, IOException {
Romain Guy693599f2010-03-23 10:58:18 -07001189
Mike Cleronb87bd162009-10-30 16:36:56 -07001190 String packageName = a.getString(R.styleable.Favorite_packageName);
1191 String className = a.getString(R.styleable.Favorite_className);
1192
1193 if (packageName == null || className == null) {
1194 return false;
1195 }
Romain Guy693599f2010-03-23 10:58:18 -07001196
1197 boolean hasPackage = true;
Mike Cleronb87bd162009-10-30 16:36:56 -07001198 ComponentName cn = new ComponentName(packageName, className);
Romain Guy693599f2010-03-23 10:58:18 -07001199 try {
1200 packageManager.getReceiverInfo(cn, 0);
1201 } catch (Exception e) {
1202 String[] packages = packageManager.currentToCanonicalPackageNames(
1203 new String[] { packageName });
1204 cn = new ComponentName(packages[0], className);
1205 try {
1206 packageManager.getReceiverInfo(cn, 0);
1207 } catch (Exception e1) {
1208 hasPackage = false;
1209 }
1210 }
1211
1212 if (hasPackage) {
1213 int spanX = a.getInt(R.styleable.Favorite_spanX, 0);
1214 int spanY = a.getInt(R.styleable.Favorite_spanY, 0);
Winson Chungb3302ae2012-05-01 10:19:14 -07001215
1216 // Read the extras
1217 Bundle extras = new Bundle();
1218 int widgetDepth = parser.getDepth();
1219 while ((type = parser.next()) != XmlPullParser.END_TAG ||
1220 parser.getDepth() > widgetDepth) {
1221 if (type != XmlPullParser.START_TAG) {
1222 continue;
1223 }
1224
1225 TypedArray ar = mContext.obtainStyledAttributes(attrs, R.styleable.Extra);
1226 if (TAG_EXTRA.equals(parser.getName())) {
1227 String key = ar.getString(R.styleable.Extra_key);
1228 String value = ar.getString(R.styleable.Extra_value);
1229 if (key != null && value != null) {
1230 extras.putString(key, value);
1231 } else {
1232 throw new RuntimeException("Widget extras must have a key and value");
1233 }
1234 } else {
1235 throw new RuntimeException("Widgets can contain only extras");
1236 }
1237 ar.recycle();
1238 }
1239
1240 return addAppWidget(db, values, cn, spanX, spanY, extras);
Romain Guy693599f2010-03-23 10:58:18 -07001241 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -07001242
Romain Guy693599f2010-03-23 10:58:18 -07001243 return false;
Bjorn Bringert7984c942009-12-09 15:38:25 +00001244 }
Bjorn Bringert7984c942009-12-09 15:38:25 +00001245 private boolean addAppWidget(SQLiteDatabase db, ContentValues values, ComponentName cn,
Winson Chungb3302ae2012-05-01 10:19:14 -07001246 int spanX, int spanY, Bundle extras) {
Mike Cleronb87bd162009-10-30 16:36:56 -07001247 boolean allocatedAppWidgets = false;
1248 final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext);
1249
1250 try {
1251 int appWidgetId = mAppWidgetHost.allocateAppWidgetId();
Daniel Lehmannc3a80402012-04-23 21:35:11 -07001252
Mike Cleronb87bd162009-10-30 16:36:56 -07001253 values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_APPWIDGET);
Bjorn Bringert7984c942009-12-09 15:38:25 +00001254 values.put(Favorites.SPANX, spanX);
1255 values.put(Favorites.SPANY, spanY);
Mike Cleronb87bd162009-10-30 16:36:56 -07001256 values.put(Favorites.APPWIDGET_ID, appWidgetId);
Adam Cohendcd297f2013-06-18 13:13:40 -07001257 values.put(Favorites._ID, generateNewItemId());
Michael Jurkaa8c760d2011-04-28 14:59:33 -07001258 dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values);
Mike Cleronb87bd162009-10-30 16:36:56 -07001259
1260 allocatedAppWidgets = true;
Daniel Lehmannc3a80402012-04-23 21:35:11 -07001261
Michael Jurka8b805b12012-04-18 14:23:14 -07001262 // TODO: need to check return value
1263 appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, cn);
Winson Chungb3302ae2012-05-01 10:19:14 -07001264
1265 // Send a broadcast to configure the widget
1266 if (extras != null && !extras.isEmpty()) {
1267 Intent intent = new Intent(ACTION_APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE);
1268 intent.setComponent(cn);
1269 intent.putExtras(extras);
1270 intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
1271 mContext.sendBroadcast(intent);
1272 }
Mike Cleronb87bd162009-10-30 16:36:56 -07001273 } catch (RuntimeException ex) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -08001274 Log.e(TAG, "Problem allocating appWidgetId", ex);
Mike Cleronb87bd162009-10-30 16:36:56 -07001275 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -07001276
Mike Cleronb87bd162009-10-30 16:36:56 -07001277 return allocatedAppWidgets;
1278 }
Adam Cohen228da5a2011-07-27 22:23:47 -07001279
1280 private long addUriShortcut(SQLiteDatabase db, ContentValues values,
Mike Cleronb87bd162009-10-30 16:36:56 -07001281 TypedArray a) {
1282 Resources r = mContext.getResources();
1283
1284 final int iconResId = a.getResourceId(R.styleable.Favorite_icon, 0);
1285 final int titleResId = a.getResourceId(R.styleable.Favorite_title, 0);
1286
Romain Guy7eb9e5e2009-12-02 20:10:07 -08001287 Intent intent;
Mike Cleronb87bd162009-10-30 16:36:56 -07001288 String uri = null;
1289 try {
1290 uri = a.getString(R.styleable.Favorite_uri);
1291 intent = Intent.parseUri(uri, 0);
1292 } catch (URISyntaxException e) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -08001293 Log.w(TAG, "Shortcut has malformed uri: " + uri);
Adam Cohen228da5a2011-07-27 22:23:47 -07001294 return -1; // Oh well
Mike Cleronb87bd162009-10-30 16:36:56 -07001295 }
1296
1297 if (iconResId == 0 || titleResId == 0) {
Joe Onoratoa30ce8e2009-11-11 08:16:49 -08001298 Log.w(TAG, "Shortcut is missing title or icon resource ID");
Adam Cohen228da5a2011-07-27 22:23:47 -07001299 return -1;
Mike Cleronb87bd162009-10-30 16:36:56 -07001300 }
1301
Adam Cohendcd297f2013-06-18 13:13:40 -07001302 long id = generateNewItemId();
Mike Cleronb87bd162009-10-30 16:36:56 -07001303 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1304 values.put(Favorites.INTENT, intent.toUri(0));
1305 values.put(Favorites.TITLE, r.getString(titleResId));
1306 values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_SHORTCUT);
1307 values.put(Favorites.SPANX, 1);
1308 values.put(Favorites.SPANY, 1);
1309 values.put(Favorites.ICON_TYPE, Favorites.ICON_TYPE_RESOURCE);
1310 values.put(Favorites.ICON_PACKAGE, mContext.getPackageName());
1311 values.put(Favorites.ICON_RESOURCE, r.getResourceName(iconResId));
Adam Cohen228da5a2011-07-27 22:23:47 -07001312 values.put(Favorites._ID, id);
Mike Cleronb87bd162009-10-30 16:36:56 -07001313
Adam Cohen228da5a2011-07-27 22:23:47 -07001314 if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values) < 0) {
1315 return -1;
1316 }
1317 return id;
Mike Cleronb87bd162009-10-30 16:36:56 -07001318 }
1319 }
Daniel Lehmannc3a80402012-04-23 21:35:11 -07001320
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001321 /**
1322 * Build a query string that will match any row where the column matches
1323 * anything in the values list.
1324 */
1325 static String buildOrWhereString(String column, int[] values) {
1326 StringBuilder selectWhere = new StringBuilder();
1327 for (int i = values.length - 1; i >= 0; i--) {
1328 selectWhere.append(column).append("=").append(values[i]);
1329 if (i > 0) {
1330 selectWhere.append(" OR ");
1331 }
1332 }
1333 return selectWhere.toString();
1334 }
1335
1336 static class SqlArguments {
1337 public final String table;
1338 public final String where;
1339 public final String[] args;
1340
1341 SqlArguments(Uri url, String where, String[] args) {
1342 if (url.getPathSegments().size() == 1) {
1343 this.table = url.getPathSegments().get(0);
1344 this.where = where;
1345 this.args = args;
1346 } else if (url.getPathSegments().size() != 2) {
1347 throw new IllegalArgumentException("Invalid URI: " + url);
1348 } else if (!TextUtils.isEmpty(where)) {
1349 throw new UnsupportedOperationException("WHERE clause not supported: " + url);
1350 } else {
1351 this.table = url.getPathSegments().get(0);
Daniel Lehmannc3a80402012-04-23 21:35:11 -07001352 this.where = "_id=" + ContentUris.parseId(url);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001353 this.args = null;
1354 }
1355 }
1356
1357 SqlArguments(Uri url) {
1358 if (url.getPathSegments().size() == 1) {
1359 table = url.getPathSegments().get(0);
1360 where = null;
1361 args = null;
1362 } else {
1363 throw new IllegalArgumentException("Invalid URI: " + url);
1364 }
1365 }
1366 }
1367}