/* * Copyright (C) 2008 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.launcher3; import android.annotation.TargetApi; import android.appwidget.AppWidgetManager; import android.content.ComponentName; import android.content.ContentProvider; import android.content.ContentProviderOperation; import android.content.ContentProviderResult; import android.content.ContentUris; import android.content.ContentValues; import android.content.OperationApplicationException; import android.database.Cursor; import android.database.sqlite.SQLiteQueryBuilder; import android.net.Uri; import android.os.Binder; import android.os.Build; import android.os.Bundle; import android.os.Process; import android.text.TextUtils; import android.util.Log; import com.android.launcher3.LauncherSettings.Favorites; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.model.ModelDbController; import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction; import com.android.launcher3.widget.LauncherWidgetHolder; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; public class LauncherProvider extends ContentProvider { private static final String TAG = "LauncherProvider"; public static final String AUTHORITY = BuildConfig.APPLICATION_ID + ".settings"; /** * $ adb shell dumpsys activity provider com.android.launcher3 */ @Override public void dump(FileDescriptor fd, PrintWriter writer, String[] args) { LauncherAppState appState = LauncherAppState.getInstanceNoCreate(); if (appState == null || !appState.getModel().isModelLoaded()) { return; } appState.getModel().dumpState("", fd, writer, args); } @Override public boolean onCreate() { if (FeatureFlags.IS_STUDIO_BUILD) { Log.d(TAG, "Launcher process started"); } // The content provider exists for the entire duration of the launcher main process and // is the first component to get created. MainProcessInitializer.initialize(getContext().getApplicationContext()); return true; } public ModelDbController getModelDbController() { return LauncherAppState.getInstance(getContext()).getModel().getModelDbController(); } @Override public String getType(Uri uri) { SqlArguments args = new SqlArguments(uri, null, null); if (TextUtils.isEmpty(args.where)) { return "vnd.android.cursor.dir/" + args.table; } else { return "vnd.android.cursor.item/" + args.table; } } @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { SqlArguments args = new SqlArguments(uri, selection, selectionArgs); SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); qb.setTables(args.table); Cursor result = getModelDbController().query( args.table, projection, args.where, args.args, sortOrder); result.setNotificationUri(getContext().getContentResolver(), uri); return result; } private void reloadLauncherIfExternal() { if (Binder.getCallingPid() != Process.myPid()) { LauncherAppState app = LauncherAppState.getInstanceNoCreate(); if (app != null) { app.getModel().forceReload(); } } } @Override public Uri insert(Uri uri, ContentValues initialValues) { // In very limited cases, we support system|signature permission apps to modify the db. if (Binder.getCallingPid() != Process.myPid()) { if (!initializeExternalAdd(initialValues)) { return null; } } SqlArguments args = new SqlArguments(uri); int rowId = getModelDbController().insert(args.table, initialValues); if (rowId < 0) return null; uri = ContentUris.withAppendedId(uri, rowId); reloadLauncherIfExternal(); return uri; } private boolean initializeExternalAdd(ContentValues values) { // 1. Ensure that externally added items have a valid item id int id = getModelDbController().generateNewItemId(); values.put(LauncherSettings.Favorites._ID, id); // 2. In the case of an app widget, and if no app widget id is specified, we // attempt allocate and bind the widget. Integer itemType = values.getAsInteger(LauncherSettings.Favorites.ITEM_TYPE); if (itemType != null && itemType.intValue() == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET && !values.containsKey(LauncherSettings.Favorites.APPWIDGET_ID)) { final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(getContext()); ComponentName cn = ComponentName.unflattenFromString( values.getAsString(Favorites.APPWIDGET_PROVIDER)); if (cn != null) { LauncherWidgetHolder widgetHolder = LauncherWidgetHolder.newInstance(getContext()); try { int appWidgetId = widgetHolder.allocateAppWidgetId(); values.put(LauncherSettings.Favorites.APPWIDGET_ID, appWidgetId); if (!appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId,cn)) { widgetHolder.deleteAppWidgetId(appWidgetId); return false; } } catch (RuntimeException e) { Log.e(TAG, "Failed to initialize external widget", e); return false; } finally { // Necessary to destroy the holder to free up possible activity context widgetHolder.destroy(); } } else { return false; } } return true; } @Override public int bulkInsert(Uri uri, ContentValues[] values) { SqlArguments args = new SqlArguments(uri); getModelDbController().bulkInsert(args.table, values); reloadLauncherIfExternal(); return values.length; } @TargetApi(Build.VERSION_CODES.M) @Override public ContentProviderResult[] applyBatch(ArrayList operations) throws OperationApplicationException { try (SQLiteTransaction t = getModelDbController().newTransaction()) { final int numOperations = operations.size(); final ContentProviderResult[] results = new ContentProviderResult[numOperations]; for (int i = 0; i < numOperations; i++) { ContentProviderOperation op = operations.get(i); results[i] = op.apply(this, results, i); } t.commit(); reloadLauncherIfExternal(); return results; } } @Override public int delete(Uri uri, String selection, String[] selectionArgs) { SqlArguments args = new SqlArguments(uri, selection, selectionArgs); int count = getModelDbController().delete(args.table, args.where, args.args); if (count > 0) { reloadLauncherIfExternal(); } return count; } @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { SqlArguments args = new SqlArguments(uri, selection, selectionArgs); int count = getModelDbController().update(args.table, values, args.where, args.args); reloadLauncherIfExternal(); return count; } @Override public Bundle call(String method, final String arg, final Bundle extras) { if (Binder.getCallingUid() != Process.myUid()) { return null; } switch (method) { case LauncherSettings.Settings.METHOD_CLEAR_EMPTY_DB_FLAG: { getModelDbController().clearEmptyDbFlag(); return null; } case LauncherSettings.Settings.METHOD_DELETE_EMPTY_FOLDERS: { Bundle result = new Bundle(); result.putIntArray(LauncherSettings.Settings.EXTRA_VALUE, getModelDbController().deleteEmptyFolders().toArray()); return result; } case LauncherSettings.Settings.METHOD_NEW_ITEM_ID: { Bundle result = new Bundle(); result.putInt(LauncherSettings.Settings.EXTRA_VALUE, getModelDbController().generateNewItemId()); return result; } case LauncherSettings.Settings.METHOD_NEW_SCREEN_ID: { Bundle result = new Bundle(); result.putInt(LauncherSettings.Settings.EXTRA_VALUE, getModelDbController().getNewScreenId()); return result; } case LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB: { getModelDbController().createEmptyDB(); return null; } case LauncherSettings.Settings.METHOD_LOAD_DEFAULT_FAVORITES: { getModelDbController().loadDefaultFavoritesIfNecessary(); return null; } case LauncherSettings.Settings.METHOD_REMOVE_GHOST_WIDGETS: { getModelDbController().removeGhostWidgets(); return null; } case LauncherSettings.Settings.METHOD_NEW_TRANSACTION: { Bundle result = new Bundle(); result.putBinder(LauncherSettings.Settings.EXTRA_VALUE, getModelDbController().newTransaction()); return result; } case LauncherSettings.Settings.METHOD_REFRESH_HOTSEAT_RESTORE_TABLE: { getModelDbController().refreshHotseatRestoreTable(); return null; } } return null; } static class SqlArguments { public final String table; public final String where; public final String[] args; SqlArguments(Uri url, String where, String[] args) { if (url.getPathSegments().size() == 1) { this.table = url.getPathSegments().get(0); this.where = where; this.args = args; } else if (url.getPathSegments().size() != 2) { throw new IllegalArgumentException("Invalid URI: " + url); } else if (!TextUtils.isEmpty(where)) { throw new UnsupportedOperationException("WHERE clause not supported: " + url); } else { this.table = url.getPathSegments().get(0); this.where = "_id=" + ContentUris.parseId(url); this.args = null; } } SqlArguments(Uri url) { if (url.getPathSegments().size() == 1) { table = url.getPathSegments().get(0); where = null; args = null; } else { throw new IllegalArgumentException("Invalid URI: " + url); } } } }