From a7ce166b2e06c834ac7d90ba0f4db16a84d10171 Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Tue, 31 May 2016 15:01:35 -0700 Subject: [PATCH] Making the security checks and policy changes only when launching custom shortcuts. This prevents launcher specific intents to unknowingly leak data. Change-Id: I01343a9cee1f2a9a37a20aaf24e933e2771ecdbc --- src/com/android/launcher3/AppInfo.java | 1 + src/com/android/launcher3/Launcher.java | 213 +++++++++----------- src/com/android/launcher3/ShortcutInfo.java | 1 + 3 files changed, 101 insertions(+), 114 deletions(-) diff --git a/src/com/android/launcher3/AppInfo.java b/src/com/android/launcher3/AppInfo.java index 28fd268cfe..e0694f3cfa 100644 --- a/src/com/android/launcher3/AppInfo.java +++ b/src/com/android/launcher3/AppInfo.java @@ -66,6 +66,7 @@ public class AppInfo extends ItemInfo { itemType = LauncherSettings.BaseLauncherColumns.ITEM_TYPE_SHORTCUT; } + @Override public Intent getIntent() { return intent; } diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index 1c94950c63..a504250aa4 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -91,6 +91,7 @@ import android.widget.TextView; import android.widget.Toast; import com.android.launcher3.DropTarget.DragObject; +import com.android.launcher3.LauncherSettings.Favorites; import com.android.launcher3.allapps.AllAppsContainerView; import com.android.launcher3.allapps.AllAppsTransitionController; import com.android.launcher3.allapps.DefaultAppSearchController; @@ -108,13 +109,13 @@ import com.android.launcher3.dynamicui.ExtractedColors; import com.android.launcher3.folder.Folder; import com.android.launcher3.folder.FolderIcon; import com.android.launcher3.keyboard.ViewGroupFocusHelper; +import com.android.launcher3.logging.FileLog; import com.android.launcher3.logging.LoggerUtils; import com.android.launcher3.logging.UserEventDispatcher; import com.android.launcher3.model.WidgetsModel; import com.android.launcher3.pageindicators.PageIndicatorLine; import com.android.launcher3.userevent.nano.LauncherLogProto; import com.android.launcher3.util.ComponentKey; -import com.android.launcher3.logging.FileLog; import com.android.launcher3.util.PackageManagerHelper; import com.android.launcher3.util.TestingUtils; import com.android.launcher3.util.Thunk; @@ -2654,26 +2655,13 @@ public class Launcher extends Activity startAppShortcutOrInfoActivity(v); } - @Thunk void startAppShortcutOrInfoActivity(View v) { - Object tag = v.getTag(); - final ShortcutInfo shortcut; - final Intent intent; - if (tag instanceof ShortcutInfo) { - shortcut = (ShortcutInfo) tag; - intent = shortcut.intent; - int[] pos = new int[2]; - v.getLocationOnScreen(pos); - intent.setSourceBounds(new Rect(pos[0], pos[1], - pos[0] + v.getWidth(), pos[1] + v.getHeight())); - - } else if (tag instanceof AppInfo) { - shortcut = null; - intent = ((AppInfo) tag).intent; - } else { - throw new IllegalArgumentException("Input must be a Shortcut or AppInfo"); + private void startAppShortcutOrInfoActivity(View v) { + ItemInfo item = (ItemInfo) v.getTag(); + Intent intent = item.getIntent(); + if (intent == null) { + throw new IllegalArgumentException("Input must have a valid intent"); } - - boolean success = startActivitySafely(v, intent, tag); + boolean success = startActivitySafely(v, intent, item); getUserEventDispatcher().logAppLaunch(v, intent); if (success && v instanceof BubbleTextView) { @@ -2813,112 +2801,109 @@ public class Launcher extends Activity } } - private boolean startActivity(View v, Intent intent, Object tag) { - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + private void startShortcutIntentSafely(Intent intent, Bundle optsBundle, ItemInfo info) { try { - // Only launch using the new animation if the shortcut has not opted out (this is a - // private contract between launcher and may be ignored in the future). - boolean useLaunchAnimation = (v != null) && - !intent.hasExtra(INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION); - LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(this); - UserManagerCompat userManager = UserManagerCompat.getInstance(this); - - UserHandleCompat user = null; - if (intent.hasExtra(AppInfo.EXTRA_PROFILE)) { - long serialNumber = intent.getLongExtra(AppInfo.EXTRA_PROFILE, -1); - user = userManager.getUserForSerialNumber(serialNumber); + StrictMode.VmPolicy oldPolicy = StrictMode.getVmPolicy(); + try { + // Temporarily disable deathPenalty on all default checks. For eg, shortcuts + // containing file Uri's would cause a crash as penaltyDeathOnFileUriExposure + // is enabled by default on NYC. + StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().detectAll() + .penaltyLog().build()); + // Could be launching some bookkeeping activity + startActivity(intent, optsBundle); + } finally { + StrictMode.setVmPolicy(oldPolicy); } - - Bundle optsBundle = null; - if (useLaunchAnimation) { - ActivityOptions opts = null; - if (Utilities.ATLEAST_MARSHMALLOW) { - int left = 0, top = 0; - int width = v.getMeasuredWidth(), height = v.getMeasuredHeight(); - if (v instanceof TextView) { - // Launch from center of icon, not entire view - Drawable icon = Workspace.getTextViewIcon((TextView) v); - if (icon != null) { - Rect bounds = icon.getBounds(); - left = (width - bounds.width()) / 2; - top = v.getPaddingTop(); - width = bounds.width(); - height = bounds.height(); - } - } - opts = ActivityOptions.makeClipRevealAnimation(v, left, top, width, height); - } else if (!Utilities.ATLEAST_LOLLIPOP) { - // Below L, we use a scale up animation - opts = ActivityOptions.makeScaleUpAnimation(v, 0, 0, - v.getMeasuredWidth(), v.getMeasuredHeight()); - } else if (Utilities.ATLEAST_LOLLIPOP_MR1) { - // On L devices, we use the device default slide-up transition. - // On L MR1 devices, we a custom version of the slide-up transition which - // doesn't have the delay present in the device default. - opts = ActivityOptions.makeCustomAnimation(this, - R.anim.task_open_enter, R.anim.no_anim); - } - optsBundle = opts != null ? opts.toBundle() : null; - } - - if (user == null || user.equals(UserHandleCompat.myUserHandle())) { - StrictMode.VmPolicy oldPolicy = StrictMode.getVmPolicy(); - try { - // Temporarily disable deathPenalty on all default checks. For eg, shortcuts - // containing file Uris would cause a crash as penaltyDeathOnFileUriExposure - // is enabled by default on NYC. - StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().detectAll() - .penaltyLog().build()); - // Could be launching some bookkeeping activity - startActivity(intent, optsBundle); - } finally { - StrictMode.setVmPolicy(oldPolicy); - } - } else { - // TODO Component can be null when shortcuts are supported for secondary user - launcherApps.startActivityForProfile(intent.getComponent(), user, - intent.getSourceBounds(), optsBundle); - } - return true; } catch (SecurityException e) { - if (Utilities.ATLEAST_MARSHMALLOW && tag instanceof ItemInfo) { - // 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(Manifest.permission.CALL_PHONE) != - PackageManager.PERMISSION_GRANTED) { - // TODO: Rename sPendingAddItem to a generic name. - sPendingAddItem = preparePendingAddArgs(REQUEST_PERMISSION_CALL_PHONE, intent, - 0, (ItemInfo) tag); - requestPermissions(new String[]{Manifest.permission.CALL_PHONE}, - REQUEST_PERMISSION_CALL_PHONE); - return false; - } + // 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(Manifest.permission.CALL_PHONE) != + PackageManager.PERMISSION_GRANTED) { + // TODO: Rename sPendingAddItem to a generic name. + sPendingAddItem = preparePendingAddArgs(REQUEST_PERMISSION_CALL_PHONE, intent, + 0, info); + requestPermissions(new String[]{Manifest.permission.CALL_PHONE}, + REQUEST_PERMISSION_CALL_PHONE); + } else { + // No idea why this was thrown. + throw e; } - Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show(); - Log.e(TAG, "Launcher does not have the permission to launch " + intent + - ". Make sure to create a MAIN intent-filter for the corresponding activity " + - "or use the exported attribute for this activity. " - + "tag="+ tag + " intent=" + intent, e); } - return false; } - public boolean startActivitySafely(View v, Intent intent, Object tag) { - boolean success = false; + private Bundle getActivityLaunchOptions(View v) { + if (Utilities.ATLEAST_MARSHMALLOW) { + int left = 0, top = 0; + int width = v.getMeasuredWidth(), height = v.getMeasuredHeight(); + if (v instanceof TextView) { + // Launch from center of icon, not entire view + Drawable icon = Workspace.getTextViewIcon((TextView) v); + if (icon != null) { + Rect bounds = icon.getBounds(); + left = (width - bounds.width()) / 2; + top = v.getPaddingTop(); + width = bounds.width(); + height = bounds.height(); + } + } + return ActivityOptions.makeClipRevealAnimation(v, left, top, width, height).toBundle(); + } else if (Utilities.ATLEAST_LOLLIPOP_MR1) { + // On L devices, we use the device default slide-up transition. + // On L MR1 devices, we use a custom version of the slide-up transition which + // doesn't have the delay present in the device default. + return ActivityOptions.makeCustomAnimation( + this, R.anim.task_open_enter, R.anim.no_anim).toBundle(); + } + return null; + } + + public boolean startActivitySafely(View v, Intent intent, ItemInfo item) { if (mIsSafeModeEnabled && !Utilities.isSystemApp(this, intent)) { Toast.makeText(this, R.string.safemode_shortcut_error, Toast.LENGTH_SHORT).show(); return false; } - try { - success = startActivity(v, intent, tag); - } catch (ActivityNotFoundException e) { - Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show(); - Log.e(TAG, "Unable to launch. tag=" + tag + " intent=" + intent, e); + // Only launch using the new animation if the shortcut has not opted out (this is a + // private contract between launcher and may be ignored in the future). + boolean useLaunchAnimation = (v != null) && + !intent.hasExtra(INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION); + Bundle optsBundle = useLaunchAnimation ? getActivityLaunchOptions(v) : null; + + UserHandleCompat user = null; + if (intent.hasExtra(AppInfo.EXTRA_PROFILE)) { + long serialNumber = intent.getLongExtra(AppInfo.EXTRA_PROFILE, -1); + user = UserManagerCompat.getInstance(this).getUserForSerialNumber(serialNumber); } - return success; + + // Prepare intent + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + if (v != null) { + int[] pos = new int[2]; + v.getLocationOnScreen(pos); + intent.setSourceBounds( + new Rect(pos[0], pos[1], pos[0] + v.getWidth(), pos[1] + v.getHeight())); + } + try { + if (Utilities.ATLEAST_MARSHMALLOW && + item != null && item.itemType == Favorites.ITEM_TYPE_SHORTCUT) { + // Shortcuts need some special checks due to legacy reasons. + startShortcutIntentSafely(intent, optsBundle, item); + } else if (user == null || user.equals(UserHandleCompat.myUserHandle())) { + // Could be launching some bookkeeping activity + startActivity(intent, optsBundle); + } else { + LauncherAppsCompat.getInstance(this).startActivityForProfile( + intent.getComponent(), user, intent.getSourceBounds(), optsBundle); + } + return true; + } catch (ActivityNotFoundException|SecurityException e) { + Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show(); + Log.e(TAG, "Unable to launch. tag=" + item + " intent=" + intent, e); + } + return false; } /** diff --git a/src/com/android/launcher3/ShortcutInfo.java b/src/com/android/launcher3/ShortcutInfo.java index 2cc9bb0ab0..d051665f32 100644 --- a/src/com/android/launcher3/ShortcutInfo.java +++ b/src/com/android/launcher3/ShortcutInfo.java @@ -144,6 +144,7 @@ public class ShortcutInfo extends ItemInfo { itemType = LauncherSettings.BaseLauncherColumns.ITEM_TYPE_SHORTCUT; } + @Override public Intent getIntent() { return intent; }