blob: 47db647e4a2a6899b3be54f1b8dec557c909b5c0 [file] [log] [blame]
The Android Open Source Projectd097a182008-12-17 18:05:58 -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
17package com.android.launcher;
18
19import android.content.ContentProvider;
20import android.content.Context;
21import android.content.ContentValues;
22import android.content.Intent;
23import android.content.ComponentName;
24import android.content.ContentUris;
25import android.content.ContentResolver;
26import android.content.pm.PackageManager;
27import android.content.pm.ActivityInfo;
28import android.database.sqlite.SQLiteOpenHelper;
29import android.database.sqlite.SQLiteDatabase;
30import android.database.sqlite.SQLiteQueryBuilder;
31import android.database.Cursor;
The Android Open Source Project15a88802009-02-10 15:44:05 -080032import android.database.SQLException;
The Android Open Source Projectd097a182008-12-17 18:05:58 -080033import android.util.Log;
34import android.util.Xml;
35import android.net.Uri;
36import android.text.TextUtils;
37import android.os.*;
38import android.provider.Settings;
39
40import java.io.FileReader;
41import java.io.File;
42import java.io.FileNotFoundException;
43import java.io.IOException;
44
45import org.xmlpull.v1.XmlPullParser;
46import org.xmlpull.v1.XmlPullParserException;
47import com.android.internal.util.XmlUtils;
48
49public class LauncherProvider extends ContentProvider {
50 private static final String LOG_TAG = "LauncherSettingsProvider";
51
52 private static final String DATABASE_NAME = "launcher.db";
The Android Open Source Project15a88802009-02-10 15:44:05 -080053
54 private static final int DATABASE_VERSION = 2;
The Android Open Source Projectd097a182008-12-17 18:05:58 -080055
56 static final String AUTHORITY = "com.android.launcher.settings";
57
58 static final String TABLE_FAVORITES = "favorites";
59 static final String PARAMETER_NOTIFY = "notify";
60
61 private SQLiteOpenHelper mOpenHelper;
62
63 @Override
64 public boolean onCreate() {
65 mOpenHelper = new DatabaseHelper(getContext());
66 return true;
67 }
68
69 @Override
70 public String getType(Uri uri) {
71 SqlArguments args = new SqlArguments(uri, null, null);
72 if (TextUtils.isEmpty(args.where)) {
73 return "vnd.android.cursor.dir/" + args.table;
74 } else {
75 return "vnd.android.cursor.item/" + args.table;
76 }
77 }
78
79 @Override
80 public Cursor query(Uri uri, String[] projection, String selection,
81 String[] selectionArgs, String sortOrder) {
82
83 SqlArguments args = new SqlArguments(uri, selection, selectionArgs);
84 SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
85 qb.setTables(args.table);
86
87 SQLiteDatabase db = mOpenHelper.getReadableDatabase();
88 Cursor result = qb.query(db, projection, args.where, args.args, null, null, sortOrder);
89 result.setNotificationUri(getContext().getContentResolver(), uri);
90
91 return result;
92 }
93
94 @Override
95 public Uri insert(Uri uri, ContentValues initialValues) {
96 SqlArguments args = new SqlArguments(uri);
97
98 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
99 final long rowId = db.insert(args.table, null, initialValues);
100 if (rowId <= 0) return null;
101
102 uri = ContentUris.withAppendedId(uri, rowId);
103 sendNotify(uri);
104
105 return uri;
106 }
107
108 @Override
109 public int bulkInsert(Uri uri, ContentValues[] values) {
110 SqlArguments args = new SqlArguments(uri);
111
112 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
113 db.beginTransaction();
114 try {
115 int numValues = values.length;
116 for (int i = 0; i < numValues; i++) {
117 if (db.insert(args.table, null, values[i]) < 0) return 0;
118 }
119 db.setTransactionSuccessful();
120 } finally {
121 db.endTransaction();
122 }
123
124 sendNotify(uri);
125 return values.length;
126 }
127
128 @Override
129 public int delete(Uri uri, String selection, String[] selectionArgs) {
130 SqlArguments args = new SqlArguments(uri, selection, selectionArgs);
131
132 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
133 int count = db.delete(args.table, args.where, args.args);
134 if (count > 0) sendNotify(uri);
135
136 return count;
137 }
138
139 @Override
140 public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
141 SqlArguments args = new SqlArguments(uri, selection, selectionArgs);
142
143 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
144 int count = db.update(args.table, values, args.where, args.args);
145 if (count > 0) sendNotify(uri);
146
147 return count;
148 }
149
150 private void sendNotify(Uri uri) {
151 String notify = uri.getQueryParameter(PARAMETER_NOTIFY);
152 if (notify == null || "true".equals(notify)) {
153 getContext().getContentResolver().notifyChange(uri, null);
154 }
155 }
156
157 private static class DatabaseHelper extends SQLiteOpenHelper {
158 /**
159 * Path to file containing default favorite packages, relative to ANDROID_ROOT.
160 */
161 private static final String DEFAULT_FAVORITES_PATH = "etc/favorites.xml";
162
163 private static final String TAG_FAVORITES = "favorites";
164 private static final String TAG_FAVORITE = "favorite";
165 private static final String TAG_PACKAGE = "package";
166 private static final String TAG_CLASS = "class";
167
168 private static final String ATTRIBUTE_SCREEN = "screen";
169 private static final String ATTRIBUTE_X = "x";
170 private static final String ATTRIBUTE_Y = "y";
171
172 private final Context mContext;
173
174 DatabaseHelper(Context context) {
175 super(context, DATABASE_NAME, null, DATABASE_VERSION);
176 mContext = context;
177 }
178
179 @Override
180 public void onCreate(SQLiteDatabase db) {
181 db.execSQL("CREATE TABLE favorites (" +
182 "_id INTEGER PRIMARY KEY," +
183 "title TEXT," +
184 "intent TEXT," +
185 "container INTEGER," +
186 "screen INTEGER," +
187 "cellX INTEGER," +
188 "cellY INTEGER," +
189 "spanX INTEGER," +
190 "spanY INTEGER," +
191 "itemType INTEGER," +
The Android Open Source Project15a88802009-02-10 15:44:05 -0800192 "gadgetId INTEGER NOT NULL DEFAULT -1," +
The Android Open Source Projectd097a182008-12-17 18:05:58 -0800193 "isShortcut INTEGER," +
194 "iconType INTEGER," +
195 "iconPackage TEXT," +
196 "iconResource TEXT," +
197 "icon BLOB," +
198 "uri TEXT," +
199 "displayMode INTEGER" +
200 ");");
201
The Android Open Source Project15a88802009-02-10 15:44:05 -0800202 // TODO: During first database creation, trigger wipe of any gadgets that
203 // might have been left around during a wipe-data.
204// GadgetManager gadgetManager = GadgetManager.getInstance(mContext);
205
206
The Android Open Source Projectd097a182008-12-17 18:05:58 -0800207 if (!convertDatabase(db)) {
208 // Populate favorites table with initial favorites
209 loadFavorites(db, DEFAULT_FAVORITES_PATH);
210 }
211 }
212
213 private boolean convertDatabase(SQLiteDatabase db) {
214 boolean converted = false;
215
216 final Uri uri = Uri.parse("content://" + Settings.AUTHORITY +
The Android Open Source Project15a88802009-02-10 15:44:05 -0800217 "/old_favorites?notify=true");
The Android Open Source Projectd097a182008-12-17 18:05:58 -0800218 final ContentResolver resolver = mContext.getContentResolver();
219 Cursor cursor = null;
220
221 try {
222 cursor = resolver.query(uri, null, null, null, null);
223 } catch (Exception e) {
224 // Ignore
225 }
226
227 // We already have a favorites database in the old provider
228 if (cursor != null && cursor.getCount() > 0) {
229 try {
230 converted = copyFromCursor(db, cursor) > 0;
231 } finally {
232 cursor.close();
233 }
234
235 if (converted) {
236 resolver.delete(uri, null, null);
237 }
238 }
239
240 return converted;
241 }
242
243 private int copyFromCursor(SQLiteDatabase db, Cursor c) {
244 final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ID);
245 final int intentIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.INTENT);
246 final int titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE);
247 final int iconTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_TYPE);
248 final int iconIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON);
249 final int iconPackageIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_PACKAGE);
250 final int iconResourceIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_RESOURCE);
251 final int containerIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER);
252 final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE);
253 final int screenIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN);
254 final int cellXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX);
255 final int cellYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY);
256 final int uriIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.URI);
257 final int displayModeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.DISPLAY_MODE);
258
259 ContentValues[] rows = new ContentValues[c.getCount()];
260 int i = 0;
261 while (c.moveToNext()) {
262 ContentValues values = new ContentValues(c.getColumnCount());
263 values.put(LauncherSettings.Favorites.ID, c.getLong(idIndex));
264 values.put(LauncherSettings.Favorites.INTENT, c.getString(intentIndex));
265 values.put(LauncherSettings.Favorites.TITLE, c.getString(titleIndex));
266 values.put(LauncherSettings.Favorites.ICON_TYPE, c.getInt(iconTypeIndex));
267 values.put(LauncherSettings.Favorites.ICON, c.getBlob(iconIndex));
268 values.put(LauncherSettings.Favorites.ICON_PACKAGE, c.getString(iconPackageIndex));
269 values.put(LauncherSettings.Favorites.ICON_RESOURCE, c.getString(iconResourceIndex));
270 values.put(LauncherSettings.Favorites.CONTAINER, c.getInt(containerIndex));
271 values.put(LauncherSettings.Favorites.ITEM_TYPE, c.getInt(itemTypeIndex));
The Android Open Source Project15a88802009-02-10 15:44:05 -0800272 values.put(LauncherSettings.Favorites.GADGET_ID, -1);
The Android Open Source Projectd097a182008-12-17 18:05:58 -0800273 values.put(LauncherSettings.Favorites.SCREEN, c.getInt(screenIndex));
274 values.put(LauncherSettings.Favorites.CELLX, c.getInt(cellXIndex));
275 values.put(LauncherSettings.Favorites.CELLY, c.getInt(cellYIndex));
276 values.put(LauncherSettings.Favorites.URI, c.getString(uriIndex));
277 values.put(LauncherSettings.Favorites.DISPLAY_MODE, c.getInt(displayModeIndex));
278 rows[i++] = values;
279 }
280
281 db.beginTransaction();
282 int total = 0;
283 try {
284 int numValues = rows.length;
285 for (i = 0; i < numValues; i++) {
286 if (db.insert(TABLE_FAVORITES, null, rows[i]) < 0) {
287 return 0;
288 } else {
289 total++;
290 }
291 }
292 db.setTransactionSuccessful();
293 } finally {
294 db.endTransaction();
295 }
296
297 return total;
298 }
299
300 @Override
301 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
The Android Open Source Project15a88802009-02-10 15:44:05 -0800302 int version = oldVersion;
303 if (version == 1) {
304 // upgrade 1 -> 2 added gadgetId column
305 db.beginTransaction();
306 try {
307 // TODO: convert any existing widgets for search and clock
308 // this might involve a FORCE_ADD_GADGET permission in GadgetManager that
309 // Launcher could then use to add these gadgets without user interaction
310 db.execSQL("ALTER TABLE favorites " +
311 "ADD COLUMN gadgetId INTEGER NOT NULL DEFAULT -1;");
312 db.setTransactionSuccessful();
313 version = 2;
314 } catch (SQLException ex) {
315 // Old version remains, which means we wipe old data
316 Log.e(LOG_TAG, ex.getMessage(), ex);
317 } finally {
318 db.endTransaction();
319 }
320 }
321
322 if (version != DATABASE_VERSION) {
323 Log.w(LOG_TAG, "Destroying all old data.");
324 db.execSQL("DROP TABLE IF EXISTS " + TABLE_FAVORITES);
325 onCreate(db);
326 }
The Android Open Source Projectd097a182008-12-17 18:05:58 -0800327 }
328
329
330 /**
331 * Loads the default set of favorite packages from an xml file.
332 *
333 * @param db The database to write the values into
334 * @param subPath The relative path from ANDROID_ROOT to the file to read
335 */
336 private int loadFavorites(SQLiteDatabase db, String subPath) {
337 FileReader favReader;
338
339 // Environment.getRootDirectory() is a fancy way of saying ANDROID_ROOT or "/system".
340 final File favFile = new File(Environment.getRootDirectory(), subPath);
341 try {
342 favReader = new FileReader(favFile);
343 } catch (FileNotFoundException e) {
344 Log.e(LOG_TAG, "Couldn't find or open favorites file " + favFile);
345 return 0;
346 }
347
348 Intent intent = new Intent(Intent.ACTION_MAIN, null);
349 intent.addCategory(Intent.CATEGORY_LAUNCHER);
350 ContentValues values = new ContentValues();
351
352 PackageManager packageManager = mContext.getPackageManager();
353 ActivityInfo info;
354 int i = 0;
355 try {
356 XmlPullParser parser = Xml.newPullParser();
357 parser.setInput(favReader);
358
359 XmlUtils.beginDocument(parser, TAG_FAVORITES);
360
361 while (true) {
362 XmlUtils.nextElement(parser);
363
364 String name = parser.getName();
365 if (!TAG_FAVORITE.equals(name)) {
366 break;
367 }
368
369 String pkg = parser.getAttributeValue(null, TAG_PACKAGE);
370 String cls = parser.getAttributeValue(null, TAG_CLASS);
371 try {
372 ComponentName cn = new ComponentName(pkg, cls);
373 info = packageManager.getActivityInfo(cn, 0);
374 intent.setComponent(cn);
375 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
376 values.put(LauncherSettings.Favorites.INTENT, intent.toURI());
377 values.put(LauncherSettings.Favorites.TITLE,
378 info.loadLabel(packageManager).toString());
379 values.put(LauncherSettings.Favorites.CONTAINER,
380 LauncherSettings.Favorites.CONTAINER_DESKTOP);
381 values.put(LauncherSettings.Favorites.ITEM_TYPE,
382 LauncherSettings.Favorites.ITEM_TYPE_APPLICATION);
383 values.put(LauncherSettings.Favorites.SCREEN,
384 parser.getAttributeValue(null, ATTRIBUTE_SCREEN));
385 values.put(LauncherSettings.Favorites.CELLX,
386 parser.getAttributeValue(null, ATTRIBUTE_X));
387 values.put(LauncherSettings.Favorites.CELLY,
388 parser.getAttributeValue(null, ATTRIBUTE_Y));
389 values.put(LauncherSettings.Favorites.SPANX, 1);
390 values.put(LauncherSettings.Favorites.SPANY, 1);
391 db.insert(TABLE_FAVORITES, null, values);
392 i++;
393 } catch (PackageManager.NameNotFoundException e) {
394 Log.w(LOG_TAG, "Unable to add favorite: " + pkg + "/" + cls, e);
395 }
396 }
397 } catch (XmlPullParserException e) {
398 Log.w(LOG_TAG, "Got exception parsing favorites.", e);
399 } catch (IOException e) {
400 Log.w(LOG_TAG, "Got exception parsing favorites.", e);
401 }
The Android Open Source Project15a88802009-02-10 15:44:05 -0800402
The Android Open Source Projectd097a182008-12-17 18:05:58 -0800403 // Add a search box
404 values.clear();
405 values.put(LauncherSettings.Favorites.CONTAINER,
406 LauncherSettings.Favorites.CONTAINER_DESKTOP);
407 values.put(LauncherSettings.Favorites.ITEM_TYPE,
408 LauncherSettings.Favorites.ITEM_TYPE_WIDGET_SEARCH);
409 values.put(LauncherSettings.Favorites.SCREEN, 2);
410 values.put(LauncherSettings.Favorites.CELLX, 0);
411 values.put(LauncherSettings.Favorites.CELLY, 0);
412 values.put(LauncherSettings.Favorites.SPANX, 4);
413 values.put(LauncherSettings.Favorites.SPANY, 1);
414 db.insert(TABLE_FAVORITES, null, values);
The Android Open Source Project15a88802009-02-10 15:44:05 -0800415
416 // TODO: automatically add clock and search gadget to
417 // default locations. this might need the FORCE permission mentioned above
The Android Open Source Projectd097a182008-12-17 18:05:58 -0800418
419 return i;
420 }
421 }
422
423 static class SqlArguments {
424 public final String table;
425 public final String where;
426 public final String[] args;
427
428 SqlArguments(Uri url, String where, String[] args) {
429 if (url.getPathSegments().size() == 1) {
430 this.table = url.getPathSegments().get(0);
431 this.where = where;
432 this.args = args;
433 } else if (url.getPathSegments().size() != 2) {
434 throw new IllegalArgumentException("Invalid URI: " + url);
435 } else if (!TextUtils.isEmpty(where)) {
436 throw new UnsupportedOperationException("WHERE clause not supported: " + url);
437 } else {
438 this.table = url.getPathSegments().get(0);
439 this.where = "_id=" + ContentUris.parseId(url);
440 this.args = null;
441 }
442 }
443
444 SqlArguments(Uri url) {
445 if (url.getPathSegments().size() == 1) {
446 table = url.getPathSegments().get(0);
447 where = null;
448 args = null;
449 } else {
450 throw new IllegalArgumentException("Invalid URI: " + url);
451 }
452 }
453 }
454}