From 84b48d8deba68d47ea17bc89b7db97b612541d8a Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Wed, 29 Mar 2023 16:52:27 -0700 Subject: [PATCH] Removing support for adding legacy shortcuts. All existing legacy shortcuts will be migrated one-time to deep shortcuts This shortcuts are pinned under the Launcher package, with custom badging Bug: 275875209 Test: Updated unit tests Flag: N/A Change-Id: I7da001f724776ad8d6c807517b7e4e259de626c2 --- res/raw/downgrade_schema.json | 7 +- res/values/strings.xml | 3 - src/com/android/launcher3/Launcher.java | 71 +------ .../android/launcher3/LauncherSettings.java | 14 -- .../launcher3/model/DatabaseHelper.java | 6 +- .../launcher3/model/DbDowngradeHelper.java | 12 +- .../android/launcher3/model/LoaderCursor.java | 22 +-- .../android/launcher3/model/ModelUtils.java | 64 ------- .../launcher3/model/PackageUpdatedTask.java | 14 -- .../launcher3/model/data/IconRequestInfo.java | 41 +--- .../model/data/WorkspaceItemInfo.java | 11 -- .../launcher3/pm/PinRequestHelper.java | 26 +-- .../pm/ShortcutConfigActivityInfo.java | 6 +- .../launcher3/provider/LauncherDbUtils.java | 91 +++++++++ .../launcher3/util/PackageManagerHelper.java | 44 ----- .../launcher3/views/ActivityContext.java | 14 +- .../model/DbDowngradeHelperTest.java | 76 +++++--- .../launcher3/model/LoaderCursorTest.java | 11 +- .../provider/LauncherDbUtilsTest.java | 176 ++++++++++++++++++ 19 files changed, 375 insertions(+), 334 deletions(-) create mode 100644 tests/src/com/android/launcher3/provider/LauncherDbUtilsTest.java diff --git a/res/raw/downgrade_schema.json b/res/raw/downgrade_schema.json index b8d0c6ff49..9929916b2d 100644 --- a/res/raw/downgrade_schema.json +++ b/res/raw/downgrade_schema.json @@ -2,8 +2,11 @@ // Note: Comments are not supported in JSON schema, but android parser is lenient. // Maximum DB version supported by this schema - "version" : 31, - + "version" : 32, + "downgrade_to_31" : [ + "ALTER TABLE favorites ADD COLUMN iconPackage TEXT;", + "ALTER TABLE favorites ADD COLUMN iconResource TEXT;" + ], "downgrade_to_30" : [], "downgrade_to_29" : [], "downgrade_to_28" : [ diff --git a/res/values/strings.xml b/res/values/strings.xml index 7552b22499..aebc1d0446 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -192,9 +192,6 @@ Allows the app to change the settings and shortcuts in home. - - %1$s is not allowed to make phone calls - diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index 4f7380a20e..59f56ff1b5 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -82,7 +82,6 @@ import android.content.Context; import android.content.Intent; import android.content.IntentSender; import android.content.SharedPreferences; -import android.content.pm.PackageManager; import android.content.res.Configuration; import android.database.sqlite.SQLiteDatabase; import android.graphics.Color; @@ -92,7 +91,6 @@ import android.os.Build; import android.os.Bundle; import android.os.CancellationSignal; import android.os.Parcelable; -import android.os.Process; import android.os.StrictMode; import android.os.SystemClock; import android.os.Trace; @@ -115,7 +113,6 @@ import android.view.ViewTreeObserver.OnPreDrawListener; import android.view.WindowManager.LayoutParams; import android.view.accessibility.AccessibilityEvent; import android.view.animation.OvershootInterpolator; -import android.widget.Toast; import android.window.BackEvent; import android.window.OnBackAnimationCallback; @@ -160,7 +157,6 @@ import com.android.launcher3.logging.InstanceIdSequence; import com.android.launcher3.logging.StatsLogManager; import com.android.launcher3.model.BgDataModel.Callbacks; import com.android.launcher3.model.ItemInstallQueue; -import com.android.launcher3.model.ModelUtils; import com.android.launcher3.model.ModelWriter; import com.android.launcher3.model.StringCache; import com.android.launcher3.model.WidgetsModel; @@ -192,7 +188,6 @@ import com.android.launcher3.util.ComponentKey; import com.android.launcher3.util.IntArray; import com.android.launcher3.util.IntSet; import com.android.launcher3.util.OnboardingPrefs; -import com.android.launcher3.util.PackageManagerHelper; import com.android.launcher3.util.PackageUserKey; import com.android.launcher3.util.PendingRequestArgs; import com.android.launcher3.util.RunnableList; @@ -263,8 +258,6 @@ public class Launcher extends StatefulActivity public static final int REQUEST_BIND_PENDING_APPWIDGET = 12; public static final int REQUEST_RECONFIGURE_APPWIDGET = 13; - private static final int REQUEST_PERMISSION_CALL_PHONE = 14; - private static final float BOUNCE_ANIMATION_TENSION = 1.3f; /** @@ -975,33 +968,6 @@ public class Launcher extends StatefulActivity handleActivityResult(requestCode, resultCode, data); } - @Override - public void onRequestPermissionsResult(int requestCode, String[] permissions, - int[] grantResults) { - PendingRequestArgs pendingArgs = mPendingRequestArgs; - if (requestCode == REQUEST_PERMISSION_CALL_PHONE && pendingArgs != null - && pendingArgs.getRequestCode() == REQUEST_PERMISSION_CALL_PHONE) { - setWaitingForResult(null); - - View v = null; - CellPos cellPos = getCellPosMapper().mapModelToPresenter(pendingArgs); - CellLayout layout = getCellLayout(pendingArgs.container, cellPos.screenId); - if (layout != null) { - v = layout.getChildAt(cellPos.cellX, cellPos.cellY); - } - Intent intent = pendingArgs.getPendingIntent(); - - if (grantResults.length > 0 - && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - startActivitySafely(v, intent, null); - } else { - // TODO: Show a snack bar with link to settings - Toast.makeText(this, getString(R.string.msg_no_phone_permission, - getString(R.string.derived_app_name)), Toast.LENGTH_SHORT).show(); - } - } - } - /** * Check to see if a given screen id exists. If not, create it at the end, return the new id. * @@ -1417,21 +1383,9 @@ public class Launcher extends StatefulActivity WorkspaceItemInfo info = PinRequestHelper.createWorkspaceItemFromPinItemRequest( this, PinRequestHelper.getPinItemRequest(data), 0); - if (info == null) { - // Legacy shortcuts are only supported for primary profile. - info = Process.myUserHandle().equals(args.user) - ? ModelUtils.fromLegacyShortcutIntent(this, data) : null; - - if (info == null) { - Log.e(TAG, "Unable to parse a valid custom shortcut result"); - return; - } else if (!new PackageManagerHelper(this).hasPermissionForActivity( - info.intent, args.getPendingIntent().getComponent().getPackageName())) { - // The app is trying to add a shortcut without sufficient permissions - Log.e(TAG, "Ignoring malicious intent " + info.intent.toUri(0)); - return; - } + Log.e(TAG, "Unable to parse a valid shortcut result"); + return; } if (container < 0) { @@ -2151,27 +2105,6 @@ public class Launcher extends StatefulActivity } } - @TargetApi(Build.VERSION_CODES.M) - @Override - public boolean onErrorStartingShortcut(Intent intent, ItemInfo info) { - // Due to legacy reasons, direct call shortcuts require Launchers to have the - // corresponding permission. Show the appropriate permission prompt if that - // is the case. - if (intent.getComponent() == null - && Intent.ACTION_CALL.equals(intent.getAction()) - && checkSelfPermission(android.Manifest.permission.CALL_PHONE) != - PackageManager.PERMISSION_GRANTED) { - - setWaitingForResult(PendingRequestArgs - .forIntent(REQUEST_PERMISSION_CALL_PHONE, intent, info)); - requestPermissions(new String[]{android.Manifest.permission.CALL_PHONE}, - REQUEST_PERMISSION_CALL_PHONE); - return true; - } else { - return false; - } - } - @Override public boolean startActivitySafely(View v, Intent intent, ItemInfo item) { if (!hasBeenResumed()) { diff --git a/src/com/android/launcher3/LauncherSettings.java b/src/com/android/launcher3/LauncherSettings.java index 1cd2a30e59..1bbb09afe4 100644 --- a/src/com/android/launcher3/LauncherSettings.java +++ b/src/com/android/launcher3/LauncherSettings.java @@ -130,18 +130,6 @@ public class LauncherSettings { */ public static final int ITEM_TYPE_SEARCH_ACTION = 9; - /** - * The icon package name in Intent.ShortcutIconResource - *

Type: TEXT

- */ - public static final String ICON_PACKAGE = "iconPackage"; - - /** - * The icon resource name in Intent.ShortcutIconResource - *

Type: TEXT

- */ - public static final String ICON_RESOURCE = "iconResource"; - /** * The custom icon bitmap. *

Type: BLOB

@@ -357,8 +345,6 @@ public class LauncherSettings { "spanY INTEGER," + "itemType INTEGER," + "appWidgetId INTEGER NOT NULL DEFAULT -1," + - "iconPackage TEXT," + - "iconResource TEXT," + "icon BLOB," + "appWidgetProvider TEXT," + "modified INTEGER NOT NULL DEFAULT 0," + diff --git a/src/com/android/launcher3/model/DatabaseHelper.java b/src/com/android/launcher3/model/DatabaseHelper.java index 3578b67864..1840b7533b 100644 --- a/src/com/android/launcher3/model/DatabaseHelper.java +++ b/src/com/android/launcher3/model/DatabaseHelper.java @@ -72,7 +72,7 @@ public class DatabaseHelper extends NoLocaleSQLiteHelper implements * Represents the schema of the database. Changes in scheme need not be backwards compatible. * When increasing the scheme version, ensure that downgrade_schema.json is updated */ - public static final int SCHEMA_VERSION = 31; + public static final int SCHEMA_VERSION = 32; private static final String TAG = "DatabaseHelper"; private static final boolean LOGD = false; @@ -327,6 +327,10 @@ public class DatabaseHelper extends NoLocaleSQLiteHelper implements return; } case 31: { + LauncherDbUtils.migrateLegacyShortcuts(mContext, db); + } + // Fall through + case 32: { // DB Upgraded successfully return; } diff --git a/src/com/android/launcher3/model/DbDowngradeHelper.java b/src/com/android/launcher3/model/DbDowngradeHelper.java index e5c44d1aad..006a9b754a 100644 --- a/src/com/android/launcher3/model/DbDowngradeHelper.java +++ b/src/com/android/launcher3/model/DbDowngradeHelper.java @@ -72,8 +72,18 @@ public class DbDowngradeHelper { } } + /** + * Creates a helper from the provided file + */ public static DbDowngradeHelper parse(File file) throws JSONException, IOException { - JSONObject obj = new JSONObject(new String(IOUtils.toByteArray(file))); + return parse(IOUtils.toByteArray(file)); + } + + /** + * Creates a helper from the provided bytes + */ + public static DbDowngradeHelper parse(byte[] fileData) throws JSONException { + JSONObject obj = new JSONObject(new String(fileData)); DbDowngradeHelper helper = new DbDowngradeHelper(obj.getInt(KEY_VERSION)); for (int version = helper.version - 1; version > 0; version--) { if (obj.has(KEY_DOWNGRADE_TO + version)) { diff --git a/src/com/android/launcher3/model/LoaderCursor.java b/src/com/android/launcher3/model/LoaderCursor.java index 855a69d4bc..c237f5b80b 100644 --- a/src/com/android/launcher3/model/LoaderCursor.java +++ b/src/com/android/launcher3/model/LoaderCursor.java @@ -75,8 +75,6 @@ public class LoaderCursor extends CursorWrapper { private final IntArray mRestoredRows = new IntArray(); private final IntSparseArrayMap mOccupied = new IntSparseArrayMap<>(); - private final int mIconPackageIndex; - private final int mIconResourceIndex; private final int mIconIndex; public final int mTitleIndex; @@ -122,8 +120,6 @@ public class LoaderCursor extends CursorWrapper { // Init column indices mIconIndex = getColumnIndexOrThrow(Favorites.ICON); - mIconPackageIndex = getColumnIndexOrThrow(Favorites.ICON_PACKAGE); - mIconResourceIndex = getColumnIndexOrThrow(Favorites.ICON_RESOURCE); mTitleIndex = getColumnIndexOrThrow(Favorites.TITLE); mIdIndex = getColumnIndexOrThrow(Favorites._ID); @@ -200,23 +196,25 @@ public class LoaderCursor extends CursorWrapper { public IconRequestInfo createIconRequestInfo( WorkspaceItemInfo wai, boolean useLowResIcon) { - String packageName = itemType == Favorites.ITEM_TYPE_SHORTCUT - ? getString(mIconPackageIndex) : null; - String resourceName = itemType == Favorites.ITEM_TYPE_SHORTCUT - ? getString(mIconResourceIndex) : null; byte[] iconBlob = itemType == Favorites.ITEM_TYPE_SHORTCUT || itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT || restoreFlag != 0 - ? getBlob(mIconIndex) : null; + ? getIconBlob() : null; - return new IconRequestInfo<>( - wai, mActivityInfo, packageName, resourceName, iconBlob, useLowResIcon); + return new IconRequestInfo<>(wai, mActivityInfo, iconBlob, useLowResIcon); + } + + /** + * Returns the icon data for at the current position + */ + public byte[] getIconBlob() { + return getBlob(mIconIndex); } /** * Returns the title or empty string */ - private String getTitle() { + public String getTitle() { return Utilities.trim(getString(mTitleIndex)); } diff --git a/src/com/android/launcher3/model/ModelUtils.java b/src/com/android/launcher3/model/ModelUtils.java index c21fc38cfb..48fb537289 100644 --- a/src/com/android/launcher3/model/ModelUtils.java +++ b/src/com/android/launcher3/model/ModelUtils.java @@ -15,18 +15,10 @@ */ package com.android.launcher3.model; -import android.content.Context; -import android.content.Intent; -import android.graphics.Bitmap; -import android.os.Process; import android.util.Log; import com.android.launcher3.LauncherSettings; -import com.android.launcher3.Utilities; -import com.android.launcher3.icons.BitmapInfo; -import com.android.launcher3.icons.LauncherIcons; import com.android.launcher3.model.data.ItemInfo; -import com.android.launcher3.model.data.WorkspaceItemInfo; import com.android.launcher3.testing.shared.TestProtocol; import com.android.launcher3.util.IntArray; import com.android.launcher3.util.IntSet; @@ -99,60 +91,4 @@ public class ModelUtils { IntStream.range(0, len).filter(i -> !seen.contains(i)).forEach(result::add); return result; } - - - /** - * Creates a workspace item info for the legacy shortcut intent - */ - @SuppressWarnings("deprecation") - public static WorkspaceItemInfo fromLegacyShortcutIntent(Context context, Intent data) { - if (!isValidExtraType(data, Intent.EXTRA_SHORTCUT_INTENT, Intent.class) - || !(isValidExtraType(data, Intent.EXTRA_SHORTCUT_ICON_RESOURCE, - Intent.ShortcutIconResource.class)) - || !(isValidExtraType(data, Intent.EXTRA_SHORTCUT_ICON, Bitmap.class))) { - - Log.e(TAG, "Invalid install shortcut intent"); - return null; - } - - Intent launchIntent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT); - String label = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME); - if (launchIntent == null || label == null) { - Log.e(TAG, "Invalid install shortcut intent"); - return null; - } - - final WorkspaceItemInfo info = new WorkspaceItemInfo(); - info.user = Process.myUserHandle(); - - BitmapInfo iconInfo = null; - try (LauncherIcons li = LauncherIcons.obtain(context)) { - Bitmap bitmap = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON); - if (bitmap != null) { - iconInfo = li.createIconBitmap(bitmap); - } else { - info.iconResource = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE); - if (info.iconResource != null) { - iconInfo = li.createIconBitmap(info.iconResource); - } - } - } - - if (iconInfo == null) { - Log.e(TAG, "Invalid icon by the app"); - return null; - } - info.bitmap = iconInfo; - info.contentDescription = info.title = Utilities.trim(label); - info.intent = launchIntent; - return info; - } - - /** - * @return true if the extra is either null or is of type {@param type} - */ - private static boolean isValidExtraType(Intent intent, String key, Class type) { - Object extra = intent.getParcelableExtra(key); - return extra == null || type.isInstance(extra); - } } diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java index 3d9d81ff4e..1ab64df4a0 100644 --- a/src/com/android/launcher3/model/PackageUpdatedTask.java +++ b/src/com/android/launcher3/model/PackageUpdatedTask.java @@ -36,9 +36,7 @@ import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherSettings; import com.android.launcher3.LauncherSettings.Favorites; import com.android.launcher3.config.FeatureFlags; -import com.android.launcher3.icons.BitmapInfo; import com.android.launcher3.icons.IconCache; -import com.android.launcher3.icons.LauncherIcons; import com.android.launcher3.logging.FileLog; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.LauncherAppWidgetInfo; @@ -204,18 +202,6 @@ public class PackageUpdatedTask extends BaseModelUpdateTask { boolean infoUpdated = false; boolean shortcutUpdated = false; - // Update shortcuts which use iconResource. - if ((si.iconResource != null) - && packageSet.contains(si.iconResource.packageName)) { - LauncherIcons li = LauncherIcons.obtain(context); - BitmapInfo iconInfo = li.createIconBitmap(si.iconResource); - li.recycle(); - if (iconInfo != null) { - si.bitmap = iconInfo; - infoUpdated = true; - } - } - ComponentName cn = si.getTargetComponent(); if (cn != null && matcher.test(si)) { String packageName = cn.getPackageName(); diff --git a/src/com/android/launcher3/model/data/IconRequestInfo.java b/src/com/android/launcher3/model/data/IconRequestInfo.java index fbf01e514e..e77e527901 100644 --- a/src/com/android/launcher3/model/data/IconRequestInfo.java +++ b/src/com/android/launcher3/model/data/IconRequestInfo.java @@ -18,16 +18,12 @@ package com.android.launcher3.model.data; import static android.graphics.BitmapFactory.decodeByteArray; import android.content.Context; -import android.content.Intent; import android.content.pm.LauncherActivityInfo; -import android.text.TextUtils; import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import com.android.launcher3.LauncherSettings; -import com.android.launcher3.icons.BitmapInfo; import com.android.launcher3.icons.LauncherIcons; /** @@ -42,8 +38,6 @@ public class IconRequestInfo { @NonNull public final T itemInfo; @Nullable public final LauncherActivityInfo launcherActivityInfo; - @Nullable public final String packageName; - @Nullable public final String resourceName; @Nullable public final byte[] iconBlob; public final boolean useLowResIcon; @@ -54,8 +48,6 @@ public class IconRequestInfo { this( itemInfo, launcherActivityInfo, - /* packageName= */ null, - /* resourceName= */ null, /* iconBlob= */ null, useLowResIcon); } @@ -63,14 +55,10 @@ public class IconRequestInfo { public IconRequestInfo( @NonNull T itemInfo, @Nullable LauncherActivityInfo launcherActivityInfo, - @Nullable String packageName, - @Nullable String resourceName, @Nullable byte[] iconBlob, boolean useLowResIcon) { this.itemInfo = itemInfo; this.launcherActivityInfo = launcherActivityInfo; - this.packageName = packageName; - this.resourceName = resourceName; this.iconBlob = iconBlob; this.useLowResIcon = useLowResIcon; } @@ -87,31 +75,16 @@ public class IconRequestInfo { try (LauncherIcons li = LauncherIcons.obtain(context)) { WorkspaceItemInfo info = (WorkspaceItemInfo) itemInfo; - if (itemInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) { - if (!TextUtils.isEmpty(packageName) || !TextUtils.isEmpty(resourceName)) { - info.iconResource = new Intent.ShortcutIconResource(); - info.iconResource.packageName = packageName; - info.iconResource.resourceName = resourceName; - BitmapInfo iconInfo = li.createIconBitmap(info.iconResource); - if (iconInfo != null) { - info.bitmap = iconInfo; - return true; - } - } - } - // Failed to load from resource, try loading from DB. - try { - if (iconBlob == null) { - return false; - } - info.bitmap = li.createIconBitmap(decodeByteArray( - iconBlob, 0, iconBlob.length)); - return true; - } catch (Exception e) { - Log.e(TAG, "Failed to decode byte array for info " + info, e); + if (iconBlob == null) { return false; } + info.bitmap = li.createIconBitmap(decodeByteArray( + iconBlob, 0, iconBlob.length)); + return true; + } catch (Exception e) { + Log.e(TAG, "Failed to decode byte array for info " + itemInfo, e); + return false; } } } diff --git a/src/com/android/launcher3/model/data/WorkspaceItemInfo.java b/src/com/android/launcher3/model/data/WorkspaceItemInfo.java index 59ef320120..01606d4611 100644 --- a/src/com/android/launcher3/model/data/WorkspaceItemInfo.java +++ b/src/com/android/launcher3/model/data/WorkspaceItemInfo.java @@ -78,12 +78,6 @@ public class WorkspaceItemInfo extends ItemInfoWithIcon { @NonNull public Intent intent; - /** - * If isShortcut=true and customIcon=false, this contains a reference to the - * shortcut icon as an application's resource. - */ - public Intent.ShortcutIconResource iconResource; - /** * A message to display when the user tries to start a disabled shortcut. * This is currently only used for deep shortcuts. @@ -109,7 +103,6 @@ public class WorkspaceItemInfo extends ItemInfoWithIcon { super(info); title = info.title; intent = new Intent(info.intent); - iconResource = info.iconResource; status = info.status; personKeys = info.personKeys.clone(); } @@ -141,10 +134,6 @@ public class WorkspaceItemInfo extends ItemInfoWithIcon { if (!usingLowResIcon()) { writer.putIcon(bitmap, user); } - if (iconResource != null) { - writer.put(Favorites.ICON_PACKAGE, iconResource.packageName) - .put(Favorites.ICON_RESOURCE, iconResource.resourceName); - } } @Override diff --git a/src/com/android/launcher3/pm/PinRequestHelper.java b/src/com/android/launcher3/pm/PinRequestHelper.java index 179061f45d..667136ae00 100644 --- a/src/com/android/launcher3/pm/PinRequestHelper.java +++ b/src/com/android/launcher3/pm/PinRequestHelper.java @@ -24,8 +24,10 @@ import android.content.Intent; import android.content.pm.LauncherApps; import android.content.pm.LauncherApps.PinItemRequest; import android.content.pm.ShortcutInfo; +import android.content.pm.ShortcutManager; import android.os.Build; import android.os.Parcelable; +import android.os.SystemClock; import androidx.annotation.Nullable; @@ -63,17 +65,10 @@ public class PinRequestHelper { } } else { // Block the worker thread until the accept() is called. - MODEL_EXECUTOR.execute(new Runnable() { - @Override - public void run() { - try { - Thread.sleep(acceptDelay); - } catch (InterruptedException e) { - // Ignore - } - if (request.isValid()) { - request.accept(); - } + MODEL_EXECUTOR.execute(() -> { + SystemClock.sleep(acceptDelay); + if (request.isValid()) { + request.accept(); } }); } @@ -95,4 +90,13 @@ public class PinRequestHelper { Parcelable extra = intent.getParcelableExtra(LauncherApps.EXTRA_PIN_ITEM_REQUEST); return extra instanceof PinItemRequest ? (PinItemRequest) extra : null; } + + /** + * Returns a PinItemRequest corresponding to the provided ShortcutInfo + */ + public static PinItemRequest createRequestForShortcut(Context context, ShortcutInfo info) { + return context.getSystemService(LauncherApps.class) + .getPinItemRequest(context.getSystemService(ShortcutManager.class) + .createShortcutResultIntent(info)); + } } diff --git a/src/com/android/launcher3/pm/ShortcutConfigActivityInfo.java b/src/com/android/launcher3/pm/ShortcutConfigActivityInfo.java index 14e67b2fff..b24ee34502 100644 --- a/src/com/android/launcher3/pm/ShortcutConfigActivityInfo.java +++ b/src/com/android/launcher3/pm/ShortcutConfigActivityInfo.java @@ -151,8 +151,6 @@ public abstract class ShortcutConfigActivityInfo implements ComponentWithLabelAn public static List queryList( Context context, @Nullable PackageUserKey packageUser) { List result = new ArrayList<>(); - UserHandle myUser = Process.myUserHandle(); - final List users; final String packageName; if (packageUser == null) { @@ -164,11 +162,9 @@ public abstract class ShortcutConfigActivityInfo implements ComponentWithLabelAn } LauncherApps launcherApps = context.getSystemService(LauncherApps.class); for (UserHandle user : users) { - boolean ignoreTargetSdk = myUser.equals(user); for (LauncherActivityInfo activityInfo : launcherApps.getShortcutConfigActivityList(packageName, user)) { - if (ignoreTargetSdk || activityInfo.getApplicationInfo().targetSdkVersion - >= Build.VERSION_CODES.O) { + if (activityInfo.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.O) { result.add(new ShortcutConfigActivityInfoVO(activityInfo)); } } diff --git a/src/com/android/launcher3/provider/LauncherDbUtils.java b/src/com/android/launcher3/provider/LauncherDbUtils.java index b5103787d4..48969fc44e 100644 --- a/src/com/android/launcher3/provider/LauncherDbUtils.java +++ b/src/com/android/launcher3/provider/LauncherDbUtils.java @@ -16,15 +16,34 @@ package com.android.launcher3.provider; +import static com.android.launcher3.icons.IconCache.EXTRA_SHORTCUT_BADGE_OVERRIDE_PACKAGE; + +import android.content.ContentValues; import android.content.Context; +import android.content.Intent; +import android.content.pm.ResolveInfo; +import android.content.pm.ShortcutInfo; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.drawable.Icon; import android.os.Binder; +import android.os.PersistableBundle; import android.os.Process; +import android.os.UserManager; +import android.text.TextUtils; +import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherSettings.Favorites; +import com.android.launcher3.Utilities; +import com.android.launcher3.model.LoaderCursor; +import com.android.launcher3.model.UserManagerState; +import com.android.launcher3.pm.PinRequestHelper; import com.android.launcher3.pm.UserCache; +import com.android.launcher3.shortcuts.ShortcutKey; import com.android.launcher3.util.IntArray; +import com.android.launcher3.util.IntSet; /** * A set of utility methods for Launcher DB used for DB updates and migration. @@ -72,6 +91,78 @@ public class LauncherDbUtils { } } + /** + * Migrates the legacy shortcuts to deep shortcuts pinned under Launcher. + * Removes any invalid shortcut or any shortcut which requires some permission to launch + */ + public static void migrateLegacyShortcuts(Context context, SQLiteDatabase db) { + Cursor c = db.query( + Favorites.TABLE_NAME, null, "itemType = 1", null, null, null, null); + UserManagerState ums = new UserManagerState(); + ums.init(UserCache.INSTANCE.get(context), + context.getSystemService(UserManager.class)); + LoaderCursor lc = new LoaderCursor(c, null, LauncherAppState.getInstance(context), ums); + IntSet deletedShortcuts = new IntSet(); + + while (lc.moveToNext()) { + if (lc.user != Process.myUserHandle()) { + deletedShortcuts.add(lc.id); + continue; + } + Intent intent = lc.parseIntent(); + if (intent == null) { + deletedShortcuts.add(lc.id); + continue; + } + + // Make sure the target intent can be launched without any permissions. Otherwise remove + // the shortcut + ResolveInfo ri = context.getPackageManager().resolveActivity(intent, 0); + if (ri == null || !TextUtils.isEmpty(ri.activityInfo.permission)) { + deletedShortcuts.add(lc.id); + continue; + } + PersistableBundle extras = new PersistableBundle(); + extras.putString(EXTRA_SHORTCUT_BADGE_OVERRIDE_PACKAGE, ri.activityInfo.packageName); + ShortcutInfo.Builder infoBuilder = new ShortcutInfo.Builder( + context, "migrated_shortcut-" + lc.id) + .setIntent(intent) + .setExtras(extras) + .setShortLabel(lc.getTitle()); + + Bitmap bitmap = null; + byte[] iconData = lc.getIconBlob(); + if (iconData != null) { + bitmap = BitmapFactory.decodeByteArray(iconData, 0, iconData.length); + } + if (bitmap != null) { + infoBuilder.setIcon(Icon.createWithBitmap(bitmap)); + } + + ShortcutInfo info = infoBuilder.build(); + if (!PinRequestHelper.createRequestForShortcut(context, info).accept()) { + deletedShortcuts.add(lc.id); + continue; + } + ContentValues update = new ContentValues(); + update.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_DEEP_SHORTCUT); + update.put(Favorites.INTENT, + ShortcutKey.makeIntent(info.getId(), context.getPackageName()).toUri(0)); + db.update(Favorites.TABLE_NAME, update, "_id = ?", + new String[] {Integer.toString(lc.id)}); + } + lc.close(); + if (!deletedShortcuts.isEmpty()) { + db.delete(Favorites.TABLE_NAME, + Utilities.createDbSelectionQuery(Favorites._ID, deletedShortcuts.getArray()), + null); + } + + // Drop the unused columns + db.execSQL("ALTER TABLE " + Favorites.TABLE_NAME + " DROP COLUMN iconPackage;"); + db.execSQL("ALTER TABLE " + Favorites.TABLE_NAME + " DROP COLUMN iconResource;"); + } + /** * Utility class to simplify managing sqlite transactions */ diff --git a/src/com/android/launcher3/util/PackageManagerHelper.java b/src/com/android/launcher3/util/PackageManagerHelper.java index a6a2751dc7..1d6bc253ec 100644 --- a/src/com/android/launcher3/util/PackageManagerHelper.java +++ b/src/com/android/launcher3/util/PackageManagerHelper.java @@ -16,7 +16,6 @@ package com.android.launcher3.util; -import android.app.AppOpsManager; import android.content.ActivityNotFoundException; import android.content.ComponentName; import android.content.Context; @@ -30,7 +29,6 @@ import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ResolveInfo; import android.graphics.Rect; import android.net.Uri; -import android.os.Build; import android.os.Bundle; import android.os.UserHandle; import android.text.TextUtils; @@ -139,48 +137,6 @@ public class PackageManagerHelper { return (info.flags & ApplicationInfo.FLAG_SUSPENDED) != 0; } - /** - * Returns true if {@param srcPackage} has the permission required to start the activity from - * {@param intent}. If {@param srcPackage} is null, then the activity should not need - * any permissions - */ - public boolean hasPermissionForActivity(Intent intent, String srcPackage) { - ResolveInfo target = mPm.resolveActivity(intent, 0); - if (target == null) { - // Not a valid target - return false; - } - if (TextUtils.isEmpty(target.activityInfo.permission)) { - // No permission is needed - return true; - } - if (TextUtils.isEmpty(srcPackage)) { - // The activity requires some permission but there is no source. - return false; - } - - // Source does not have sufficient permissions. - if(mPm.checkPermission(target.activityInfo.permission, srcPackage) != - PackageManager.PERMISSION_GRANTED) { - return false; - } - - // On M and above also check AppOpsManager for compatibility mode permissions. - if (TextUtils.isEmpty(AppOpsManager.permissionToOp(target.activityInfo.permission))) { - // There is no app-op for this permission, which could have been disabled. - return true; - } - - // There is no direct way to check if the app-op is allowed for a particular app. Since - // app-op is only enabled for apps running in compatibility mode, simply block such apps. - - try { - return mPm.getApplicationInfo(srcPackage, 0).targetSdkVersion >= Build.VERSION_CODES.M; - } catch (NameNotFoundException e) { } - - return false; - } - public Intent getMarketIntent(String packageName) { return new Intent(Intent.ACTION_VIEW) .setData(new Uri.Builder() diff --git a/src/com/android/launcher3/views/ActivityContext.java b/src/com/android/launcher3/views/ActivityContext.java index a6744fb02a..79d4df02ba 100644 --- a/src/com/android/launcher3/views/ActivityContext.java +++ b/src/com/android/launcher3/views/ActivityContext.java @@ -437,9 +437,7 @@ public interface ActivityContext { StrictMode.setVmPolicy(oldPolicy); } } catch (SecurityException e) { - if (!onErrorStartingShortcut(intent, info)) { - throw e; - } + throw e; } } @@ -459,16 +457,6 @@ public interface ActivityContext { } } - /** - * Invoked when a shortcut fails to launch. - * @param intent Shortcut intent that failed to start. - * @param info Shortcut information. - * @return {@code true} if the error is handled by this callback. - */ - default boolean onErrorStartingShortcut(Intent intent, ItemInfo info) { - return false; - } - default CellPosMapper getCellPosMapper() { return CellPosMapper.DEFAULT; } diff --git a/tests/src/com/android/launcher3/model/DbDowngradeHelperTest.java b/tests/src/com/android/launcher3/model/DbDowngradeHelperTest.java index 8a092bf402..0a1a9ba6a9 100644 --- a/tests/src/com/android/launcher3/model/DbDowngradeHelperTest.java +++ b/tests/src/com/android/launcher3/model/DbDowngradeHelperTest.java @@ -108,6 +108,21 @@ public class DbDowngradeHelperTest { assertEquals(0, mSchemaFile.lastModified()); } + @Test + public void testDowngrade_success_v31() throws Exception { + setupTestDb(); + + try (SQLiteOpenHelper helper = new MyDatabaseHelper()) { + assertFalse(hasFavoritesColumn(helper.getWritableDatabase(), "iconPackage")); + assertFalse(hasFavoritesColumn(helper.getWritableDatabase(), "iconResource")); + } + + try (TestOpenHelper helper = new TestOpenHelper(24)) { + assertTrue(hasFavoritesColumn(helper.getWritableDatabase(), "iconPackage")); + assertTrue(hasFavoritesColumn(helper.getWritableDatabase(), "iconResource")); + } + } + @Test public void testDowngrade_success_v24() throws Exception { setupTestDb(); @@ -121,34 +136,18 @@ public class DbDowngradeHelperTest { public void testDowngrade_success_v22() throws Exception { setupTestDb(); - SQLiteOpenHelper helper = new TestOpenHelper(22); - assertEquals(22, helper.getWritableDatabase().getVersion()); - - // Check column does not exist - try (Cursor c = helper.getWritableDatabase().query(Favorites.TABLE_NAME, - null, null, null, null, null, null)) { - assertEquals(-1, c.getColumnIndex(Favorites.OPTIONS)); - - // Check data is present - assertEquals(10, c.getCount()); + try (SQLiteOpenHelper helper = new TestOpenHelper(22)) { + assertEquals(22, helper.getWritableDatabase().getVersion()); + assertFalse(hasFavoritesColumn(helper.getWritableDatabase(), Favorites.OPTIONS)); + assertEquals(10, getFavoriteDataCount(helper.getWritableDatabase())); } - helper.close(); - helper = new DatabaseHelper(mContext, DB_FILE, false) { - @Override - public void onOpen(SQLiteDatabase db) { } - }; - assertEquals(DatabaseHelper.SCHEMA_VERSION, helper.getWritableDatabase().getVersion()); - - try (Cursor c = helper.getWritableDatabase().query(Favorites.TABLE_NAME, - null, null, null, null, null, null)) { - // Check column exists - assertNotSame(-1, c.getColumnIndex(Favorites.OPTIONS)); - - // Check data is present - assertEquals(10, c.getCount()); + try (SQLiteOpenHelper helper = new MyDatabaseHelper()) { + assertEquals(DatabaseHelper.SCHEMA_VERSION, + helper.getWritableDatabase().getVersion()); + assertTrue(hasFavoritesColumn(helper.getWritableDatabase(), Favorites.OPTIONS)); + assertEquals(10, getFavoriteDataCount(helper.getWritableDatabase())); } - helper.close(); } @Test(expected = DowngradeFailException.class) @@ -165,10 +164,7 @@ public class DbDowngradeHelperTest { DbDowngradeHelper.updateSchemaFile(mSchemaFile, DatabaseHelper.SCHEMA_VERSION, mContext); - DatabaseHelper dbHelper = new DatabaseHelper(mContext, DB_FILE, false) { - @Override - public void onOpen(SQLiteDatabase db) { } - }; + DatabaseHelper dbHelper = new MyDatabaseHelper(); // Insert mock data for (int i = 0; i < 10; i++) { ContentValues values = new ContentValues(); @@ -210,4 +206,26 @@ public class DbDowngradeHelperTest { super(e); } } + + private static boolean hasFavoritesColumn(SQLiteDatabase db, String columnName) { + try (Cursor c = db.query(Favorites.TABLE_NAME, null, null, null, null, null, null)) { + return c.getColumnIndex(columnName) >= 0; + } + } + + public static int getFavoriteDataCount(SQLiteDatabase db) { + try (Cursor c = db.query(Favorites.TABLE_NAME, null, null, null, null, null, null)) { + return c.getCount(); + } + } + + private class MyDatabaseHelper extends DatabaseHelper { + + MyDatabaseHelper() { + super(mContext, DB_FILE, false); + } + + @Override + public void onOpen(SQLiteDatabase db) { } + } } diff --git a/tests/src/com/android/launcher3/model/LoaderCursorTest.java b/tests/src/com/android/launcher3/model/LoaderCursorTest.java index 7ab86ad4fa..d192be408c 100644 --- a/tests/src/com/android/launcher3/model/LoaderCursorTest.java +++ b/tests/src/com/android/launcher3/model/LoaderCursorTest.java @@ -27,8 +27,6 @@ import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER; import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP; import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT; import static com.android.launcher3.LauncherSettings.Favorites.ICON; -import static com.android.launcher3.LauncherSettings.Favorites.ICON_PACKAGE; -import static com.android.launcher3.LauncherSettings.Favorites.ICON_RESOURCE; import static com.android.launcher3.LauncherSettings.Favorites.INTENT; import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE; import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION; @@ -97,11 +95,10 @@ public class LoaderCursorTest { mApp = LauncherAppState.getInstance(mContext); mCursor = new MatrixCursor(new String[] { - ICON, ICON_PACKAGE, ICON_RESOURCE, TITLE, - _ID, CONTAINER, ITEM_TYPE, PROFILE_ID, - SCREEN, CELLX, CELLY, RESTORED, INTENT, - APPWIDGET_ID, APPWIDGET_PROVIDER, SPANX, - SPANY, RANK, OPTIONS, APPWIDGET_SOURCE + ICON, TITLE, _ID, CONTAINER, ITEM_TYPE, + PROFILE_ID, SCREEN, CELLX, CELLY, RESTORED, + INTENT, APPWIDGET_ID, APPWIDGET_PROVIDER, + SPANX, SPANY, RANK, OPTIONS, APPWIDGET_SOURCE }); UserManagerState ums = new UserManagerState(); diff --git a/tests/src/com/android/launcher3/provider/LauncherDbUtilsTest.java b/tests/src/com/android/launcher3/provider/LauncherDbUtilsTest.java new file mode 100644 index 0000000000..2b6f9ff193 --- /dev/null +++ b/tests/src/com/android/launcher3/provider/LauncherDbUtilsTest.java @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2023 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.provider; + +import static android.content.pm.LauncherApps.EXTRA_PIN_ITEM_REQUEST; + +import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; + +import static com.android.launcher3.LauncherSettings.Favorites.INTENT; +import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import android.content.ContentValues; +import android.content.Context; +import android.content.ContextWrapper; +import android.content.Intent; +import android.content.pm.LauncherApps.PinItemRequest; +import android.content.pm.ShortcutInfo; +import android.content.pm.ShortcutManager; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.net.Uri; +import android.os.Process; + +import com.android.launcher3.LauncherSettings.Favorites; +import com.android.launcher3.R; +import com.android.launcher3.model.DatabaseHelper; +import com.android.launcher3.model.DbDowngradeHelper; +import com.android.launcher3.settings.SettingsActivity; +import com.android.launcher3.shortcuts.ShortcutKey; +import com.android.launcher3.util.IOUtils; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; + +import java.io.InputStream; + +/** + * Test for {@link LauncherDbUtils}. + */ +public class LauncherDbUtilsTest { + + private Context mContext; + private ArgumentCaptor mInfoArgumentCaptor; + private boolean mPinningAllowed = true; + + @Before + public void setup() { + PinItemRequest req = mock(PinItemRequest.class); + doAnswer(i -> mPinningAllowed).when(req).accept(); + + mInfoArgumentCaptor = ArgumentCaptor.forClass(ShortcutInfo.class); + ShortcutManager sm = mock(ShortcutManager.class); + when(sm.createShortcutResultIntent(mInfoArgumentCaptor.capture())) + .thenReturn(new Intent().putExtra(EXTRA_PIN_ITEM_REQUEST, req)); + + mContext = new ContextWrapper(getInstrumentation().getTargetContext()) { + + @Override + public Object getSystemService(String name) { + return SHORTCUT_SERVICE.equals(name) ? sm : super.getSystemService(name); + } + }; + } + + @Test + public void migrateLegacyShortcuts_invalidIntent() throws Exception { + SQLiteDatabase db = setupLegacyShortcut(null); + assertEquals(1, getFavoriteDataCount(db)); + + LauncherDbUtils.migrateLegacyShortcuts(mContext, db); + + assertEquals(0, getFavoriteDataCount(db)); + } + + @Test + public void migrateLegacyShortcuts_protectedIntent() throws Exception { + Intent intent = new Intent(Intent.ACTION_CALL) + .setData(Uri.parse("tel:0000000000")); + SQLiteDatabase db = setupLegacyShortcut(intent); + assertEquals(1, getFavoriteDataCount(db)); + + LauncherDbUtils.migrateLegacyShortcuts(mContext, db); + + assertEquals(0, getFavoriteDataCount(db)); + } + + @Test + public void migrateLegacyShortcuts_pinningDisabled() throws Exception { + mPinningAllowed = false; + Intent intent = new Intent(mContext, SettingsActivity.class); + SQLiteDatabase db = setupLegacyShortcut(intent); + assertEquals(1, getFavoriteDataCount(db)); + + LauncherDbUtils.migrateLegacyShortcuts(mContext, db); + + assertEquals(0, getFavoriteDataCount(db)); + } + + @Test + public void migrateLegacyShortcuts_success() throws Exception { + Intent intent = new Intent(mContext, SettingsActivity.class); + SQLiteDatabase db = setupLegacyShortcut(intent); + assertEquals(1, getFavoriteDataCount(db)); + + LauncherDbUtils.migrateLegacyShortcuts(mContext, db); + + assertEquals(1, getFavoriteDataCount(db)); + ShortcutInfo info = mInfoArgumentCaptor.getValue(); + assertNotNull(info); + assertEquals("Hello", info.getTitle()); + try (Cursor c = db.query(Favorites.TABLE_NAME, null, null, null, null, null, null)) { + c.moveToNext(); + assertEquals(Favorites.ITEM_TYPE_DEEP_SHORTCUT, c.getInt(c.getColumnIndex(ITEM_TYPE))); + + ShortcutKey key = ShortcutKey.fromIntent( + Intent.parseUri(c.getString(c.getColumnIndex(INTENT)), 0), + Process.myUserHandle()); + + assertEquals(info.getId(), key.getId()); + assertEquals(info.getPackage(), key.getPackageName()); + } + } + + private SQLiteDatabase setupLegacyShortcut(Intent intent) throws Exception { + SQLiteDatabase db = new MyDatabaseHelper().getWritableDatabase(); + try (InputStream in = mContext.getResources().openRawResource(R.raw.downgrade_schema)) { + DbDowngradeHelper.parse(IOUtils.toByteArray(in)).onDowngrade(db, db.getVersion(), 31); + } + + ContentValues cv = new ContentValues(); + cv.put("itemType", 1); + cv.put("title", "Hello"); + cv.put("intent", intent == null ? null : intent.toUri(0)); + db.insert(Favorites.TABLE_NAME, null, cv); + return db; + } + + public static int getFavoriteDataCount(SQLiteDatabase db) { + try (Cursor c = db.query(Favorites.TABLE_NAME, null, null, null, null, null, null)) { + return c.getCount(); + } + } + + private class MyDatabaseHelper extends DatabaseHelper { + + MyDatabaseHelper() { + super(mContext, null, false); + } + + @Override + protected void handleOneTimeDataUpgrade(SQLiteDatabase db) { } + + protected void onEmptyDbCreated() { } + } +}