From b0cb8f35bd71b7969de2f78c7fe6e4a4d065f45a Mon Sep 17 00:00:00 2001 From: Artem Smirnov Date: Mon, 9 Mar 2026 22:09:45 +0200 Subject: [PATCH 1/2] Move GrapheneOS shortcuts from SystemShortcut to GrapheneSystemShortcut --- .../popup/GrapheneSystemShortcut.java | 129 ++++++++++++++++++ .../uioverrides/QuickstepLauncher.java | 4 +- .../quickstep/TaskShortcutFactory.java | 7 +- src/com/android/launcher3/Launcher.java | 5 +- .../launcher3/popup/SystemShortcut.java | 84 ------------ 5 files changed, 137 insertions(+), 92 deletions(-) create mode 100644 quickstep/src/com/android/launcher3/popup/GrapheneSystemShortcut.java diff --git a/quickstep/src/com/android/launcher3/popup/GrapheneSystemShortcut.java b/quickstep/src/com/android/launcher3/popup/GrapheneSystemShortcut.java new file mode 100644 index 00000000000..42868b46697 --- /dev/null +++ b/quickstep/src/com/android/launcher3/popup/GrapheneSystemShortcut.java @@ -0,0 +1,129 @@ +package com.android.launcher3.popup; + +import android.Manifest; +import android.app.ActivityOptions; +import android.app.StorageScope; +import android.content.Intent; +import android.content.pm.GosPackageState; +import android.content.pm.GosPackageStateFlag; +import android.ext.cscopes.ContactScopesApi; +import android.view.View; +import android.window.SplashScreen; + +import androidx.annotation.Nullable; +import androidx.annotation.RequiresPermission; + +import com.android.launcher3.BaseActivity; +import com.android.launcher3.R; +import com.android.launcher3.model.data.ItemInfo; +import com.android.launcher3.views.ActivityContext; + +public interface GrapheneSystemShortcut { + + abstract class ScopedFeatureShortcut extends SystemShortcut { + + protected final String targetPackage; + + private ScopedFeatureShortcut( + int iconResId, + int labelResId, + T target, + ItemInfo itemInfo, + View originalView + ) { + super(iconResId, labelResId, target, itemInfo, originalView); + targetPackage = itemInfo.getTargetPackage(); + } + + protected static boolean hasGosPackageStateFlag(ItemInfo itemInfo, int flag) { + String pkg = itemInfo.getTargetPackage(); + if (pkg == null) { + return false; + } + return GosPackageState.get(pkg, itemInfo.user).hasFlag(flag); + } + + @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS) + @Override + public void onClick(View view) { + dismissTaskMenuView(); + + Intent intent = getIntent(targetPackage); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + var options = ActivityOptions.makeBasic() + .setSplashScreenStyle(SplashScreen.SPLASH_SCREEN_STYLE_SOLID_COLOR) + .toBundle(); + view.getContext().startActivityAsUser(intent, options, mItemInfo.user); + } + + protected abstract Intent getIntent(String targetPkg); + } + + /** + * Storage + */ + + SystemShortcut.Factory STORAGE_SCOPES = StorageScopes::maybeGet; + + class StorageScopes extends ScopedFeatureShortcut { + + private StorageScopes(T target, ItemInfo itemInfo, View originalView) { + super( + R.drawable.ic_sscopes_add_file, + R.string.storage_scopes_drop_target_label, + target, + itemInfo, + originalView + ); + } + + @Nullable + public static StorageScopes maybeGet( + T target, ItemInfo itemInfo, View originalView + ) { + if (!hasGosPackageStateFlag(itemInfo, GosPackageStateFlag.STORAGE_SCOPES_ENABLED)) { + return null; + } + return new StorageScopes<>(target, itemInfo, originalView); + } + + @Override + protected Intent getIntent(String targetPkg) { + return StorageScope.createConfigActivityIntent(targetPkg); + } + } + + /** + * Contacts + */ + + SystemShortcut.Factory CONTACT_SCOPES = ContactScopes::maybeGet; + + class ContactScopes extends ScopedFeatureShortcut { + + private ContactScopes(T target, ItemInfo itemInfo, View originalView) { + super( + R.drawable.ic_cscopes, + R.string.contact_scopes_label, + target, + itemInfo, + originalView + ); + } + + @Nullable + public static ContactScopes maybeGet( + T target, ItemInfo itemInfo, View originalView + ) { + if (!hasGosPackageStateFlag(itemInfo, GosPackageStateFlag.CONTACT_SCOPES_ENABLED)) { + return null; + } + return new ContactScopes<>(target, itemInfo, originalView); + } + + @Override + protected Intent getIntent(String targetPkg) { + return ContactScopesApi.createConfigActivityIntent(targetPkg); + } + } +} diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java index fd11dd595c9..7c496ea5fc7 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java +++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java @@ -53,15 +53,15 @@ import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SPLIT_SELECTION_EXIT_INTERRUPTED; import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_NOT_PINNABLE; import static com.android.launcher3.popup.QuickstepSystemShortcut.getSplitSelectShortcutByPosition; +import static com.android.launcher3.popup.GrapheneSystemShortcut.CONTACT_SCOPES; +import static com.android.launcher3.popup.GrapheneSystemShortcut.STORAGE_SCOPES; import static com.android.launcher3.popup.SystemShortcut.ADD_TO_HOME_SCREEN; import static com.android.launcher3.popup.SystemShortcut.APP_INFO; import static com.android.launcher3.popup.SystemShortcut.BUBBLE_SHORTCUT; -import static com.android.launcher3.popup.SystemShortcut.CONTACT_SCOPES; import static com.android.launcher3.popup.SystemShortcut.DONT_SUGGEST_APP; import static com.android.launcher3.popup.SystemShortcut.INSTALL; import static com.android.launcher3.popup.SystemShortcut.PRIVATE_PROFILE_INSTALL; import static com.android.launcher3.popup.SystemShortcut.REMOVE; -import static com.android.launcher3.popup.SystemShortcut.STORAGE_SCOPES; import static com.android.launcher3.popup.SystemShortcut.UNINSTALL_APP; import static com.android.launcher3.popup.SystemShortcut.WIDGETS; import static com.android.launcher3.taskbar.LauncherTaskbarUIController.ALL_APPS_PAGE_PROGRESS_INDEX; diff --git a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java index d4e313829bf..2d7e6e5cdd0 100644 --- a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java +++ b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java @@ -46,6 +46,7 @@ import com.android.launcher3.R; import com.android.launcher3.logging.StatsLogManager.LauncherEvent; import com.android.launcher3.model.WellbeingModel; +import com.android.launcher3.popup.GrapheneSystemShortcut; import com.android.launcher3.popup.SystemShortcut; import com.android.launcher3.popup.SystemShortcut.AppInfo; import com.android.launcher3.util.InstantAppResolver; @@ -135,7 +136,8 @@ public List getShortcuts(RecentsViewContainer container, TaskContainer taskContainer) { TaskView taskView = taskContainer.getTaskView(); - var s = SystemShortcut.StorageScopes.maybeGet(container, taskContainer.getItemInfo(), taskView); + var s = GrapheneSystemShortcut.StorageScopes.maybeGet( + container, taskContainer.getItemInfo(), taskView); if (s == null) { return null; } @@ -155,7 +157,8 @@ public List getShortcuts(RecentsViewContainer container, TaskContainer taskContainer) { TaskView taskView = taskContainer.getTaskView(); - var s = SystemShortcut.ContactScopes.maybeGet(container, taskContainer.getItemInfo(), taskView); + var s = GrapheneSystemShortcut.ContactScopes.maybeGet( + container, taskContainer.getItemInfo(), taskView); if (s == null) { return null; } diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index ec188962a8f..e82551305e1 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -2957,10 +2957,7 @@ public Stream getSupportedShortcuts(ItemInfo itemInfo) { return Stream.of(APP_INFO, WIDGETS, INSTALL); } } - return Stream.of(APP_INFO, WIDGETS, INSTALL - , com.android.launcher3.popup.SystemShortcut.STORAGE_SCOPES - , com.android.launcher3.popup.SystemShortcut.CONTACT_SCOPES - ); + return Stream.of(APP_INFO, WIDGETS, INSTALL); } /** diff --git a/src/com/android/launcher3/popup/SystemShortcut.java b/src/com/android/launcher3/popup/SystemShortcut.java index d992025bada..3b00752fe86 100644 --- a/src/com/android/launcher3/popup/SystemShortcut.java +++ b/src/com/android/launcher3/popup/SystemShortcut.java @@ -14,8 +14,6 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; -import android.content.pm.GosPackageState; -import android.content.pm.GosPackageStateFlag; import android.content.pm.ShortcutInfo; import android.graphics.Rect; import android.os.Process; @@ -25,14 +23,12 @@ import android.view.accessibility.AccessibilityNodeInfo; import android.widget.ImageView; import android.widget.TextView; -import android.window.SplashScreen; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.launcher3.AbstractFloatingView; import com.android.launcher3.AbstractFloatingViewHelper; -import com.android.launcher3.BaseActivity; import com.android.launcher3.DropTargetHandler; import com.android.launcher3.Flags; import com.android.launcher3.LauncherSettings; @@ -260,86 +256,6 @@ public SplitAccessibilityInfo(boolean containsMultipleTasks, } } - abstract static class ScopesShortcut extends SystemShortcut { - - protected String targetPackage; - - private ScopesShortcut(int icon, int label, T target, ItemInfo itemInfo, View originalView) { - super(icon, label, target, itemInfo, originalView); - targetPackage = itemInfo.getTargetPackage(); - } - - protected static boolean hasGosPackageStateFlag(ItemInfo itemInfo, int flag) { - String pkg = itemInfo.getTargetPackage(); - if (pkg == null) { - return false; - } - return GosPackageState.get(pkg, itemInfo.user).hasFlag(flag); - } - - @Override - public void onClick(View v) { - dismissTaskMenuView(); - - Intent intent = getIntent(targetPackage); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); - var opts = android.app.ActivityOptions.makeBasic() - .setSplashScreenStyle(SplashScreen.SPLASH_SCREEN_STYLE_SOLID_COLOR) - .toBundle(); - v.getContext().startActivityAsUser(intent, opts, mItemInfo.user); - } - - protected abstract Intent getIntent(String targetPkg); - } - - public static final Factory STORAGE_SCOPES = StorageScopes::maybeGet; - - public static class StorageScopes extends ScopesShortcut { - - private StorageScopes(T target, ItemInfo itemInfo, View originalView) { - super(R.drawable.ic_sscopes_add_file, R.string.storage_scopes_drop_target_label, target, - itemInfo, originalView); - } - - @Nullable - public static StorageScopes maybeGet(T target, ItemInfo itemInfo, View originalView) { - if (hasGosPackageStateFlag(itemInfo, GosPackageStateFlag.STORAGE_SCOPES_ENABLED)) { - return new StorageScopes<>(target, itemInfo, originalView); - } - - return null; - } - - @Override - protected Intent getIntent(String targetPkg) { - return android.app.StorageScope.createConfigActivityIntent(targetPkg); - } - } - - public static final Factory CONTACT_SCOPES = ContactScopes::maybeGet; - - public static class ContactScopes extends ScopesShortcut { - - private ContactScopes(T target, ItemInfo itemInfo, View originalView) { - super(R.drawable.ic_cscopes, R.string.contact_scopes_label, target, - itemInfo, originalView); - } - - @Nullable - public static ContactScopes maybeGet(T target, ItemInfo itemInfo, View originalView) { - if (hasGosPackageStateFlag(itemInfo, GosPackageStateFlag.CONTACT_SCOPES_ENABLED)) { - return new ContactScopes<>(target, itemInfo, originalView); - } - - return null; - } - - @Override - protected Intent getIntent(String targetPkg) { - return android.ext.cscopes.ContactScopesApi.createConfigActivityIntent(targetPackage); - } - } - public static final Factory REMOVE = RemoveApp::new; public static class RemoveApp extends SystemShortcut { From a08e191781a132c2643dee012075705cb8a0f65e Mon Sep 17 00:00:00 2001 From: Artem Smirnov Date: Mon, 9 Mar 2026 22:10:25 +0200 Subject: [PATCH 2/2] Add microphone spoofing shortcut for eligible apps in Quickstep --- .../popup/GrapheneSystemShortcut.java | 35 +++++++++++++++++++ .../uioverrides/QuickstepLauncher.java | 2 ++ .../android/quickstep/TaskOverlayFactory.java | 1 + .../quickstep/TaskShortcutFactory.java | 21 +++++++++++ res/drawable/ic_microphone_spoofing.xml | 11 ++++++ res/values/strings.xml | 2 ++ 6 files changed, 72 insertions(+) create mode 100644 res/drawable/ic_microphone_spoofing.xml diff --git a/quickstep/src/com/android/launcher3/popup/GrapheneSystemShortcut.java b/quickstep/src/com/android/launcher3/popup/GrapheneSystemShortcut.java index 42868b46697..f89ea135bac 100644 --- a/quickstep/src/com/android/launcher3/popup/GrapheneSystemShortcut.java +++ b/quickstep/src/com/android/launcher3/popup/GrapheneSystemShortcut.java @@ -7,6 +7,7 @@ import android.content.pm.GosPackageState; import android.content.pm.GosPackageStateFlag; import android.ext.cscopes.ContactScopesApi; +import android.ext.micspoofing.MicSpoofingApi; import android.view.View; import android.window.SplashScreen; @@ -126,4 +127,38 @@ protected Intent getIntent(String targetPkg) { return ContactScopesApi.createConfigActivityIntent(targetPkg); } } + + /** + * Mic spoofing + */ + + SystemShortcut.Factory MIC_SPOOFING = MicSpoofing::maybeGet; + + class MicSpoofing extends ScopedFeatureShortcut { + + private MicSpoofing(T target, ItemInfo itemInfo, View originalView) { + super( + R.drawable.ic_microphone_spoofing, + R.string.microphone_spoofing_drop_target_label, + target, + itemInfo, + originalView + ); + } + + @Nullable + public static MicSpoofing maybeGet( + T target, ItemInfo itemInfo, View originalView + ) { + if (!hasGosPackageStateFlag(itemInfo, GosPackageStateFlag.MIC_SPOOFING_ENABLED)) { + return null; + } + return new MicSpoofing<>(target, itemInfo, originalView); + } + + @Override + protected Intent getIntent(String targetPkg) { + return MicSpoofingApi.createConfigActivityIntent(targetPkg); + } + } } diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java index 7c496ea5fc7..f18231899f0 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java +++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java @@ -54,6 +54,7 @@ import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_NOT_PINNABLE; import static com.android.launcher3.popup.QuickstepSystemShortcut.getSplitSelectShortcutByPosition; import static com.android.launcher3.popup.GrapheneSystemShortcut.CONTACT_SCOPES; +import static com.android.launcher3.popup.GrapheneSystemShortcut.MIC_SPOOFING; import static com.android.launcher3.popup.GrapheneSystemShortcut.STORAGE_SCOPES; import static com.android.launcher3.popup.SystemShortcut.ADD_TO_HOME_SCREEN; import static com.android.launcher3.popup.SystemShortcut.APP_INFO; @@ -551,6 +552,7 @@ public Stream getSupportedShortcuts(ItemInfo itemInfo) { shortcuts.add(WIDGETS); shortcuts.add(STORAGE_SCOPES); shortcuts.add(CONTACT_SCOPES); + shortcuts.add(MIC_SPOOFING); shortcuts.add(INSTALL); // TODO(b/444744861): Update private space apps to have its own container. boolean isPinnable = itemInfo instanceof ItemInfoWithIcon info diff --git a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java index 5200870a394..9fcdee47962 100644 --- a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java +++ b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java @@ -136,6 +136,7 @@ public void clearAllActiveState() { } TaskShortcutFactory.MODAL, TaskShortcutFactory.STORAGE_SCOPES, TaskShortcutFactory.CONTACT_SCOPES, + TaskShortcutFactory.MIC_SPOOFING, }; /** diff --git a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java index 2d7e6e5cdd0..a2911d630da 100644 --- a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java +++ b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java @@ -171,6 +171,27 @@ public boolean showForGroupedTask() { } }; + TaskShortcutFactory MIC_SPOOFING = new TaskShortcutFactory() { + @Nullable + @Override + public List getShortcuts(RecentsViewContainer container, + TaskContainer taskContainer) { + TaskView taskView = taskContainer.getTaskView(); + + var s = GrapheneSystemShortcut.MicSpoofing.maybeGet( + container, taskContainer.getItemInfo(), taskView); + if (s == null) { + return null; + } + return Collections.singletonList(s); + } + + @Override + public boolean showForGroupedTask() { + return true; + } + }; + class SplitSelectSystemShortcut extends SystemShortcut { private final TaskContainer mTaskContainer; private final SplitPositionOption mSplitPositionOption; diff --git a/res/drawable/ic_microphone_spoofing.xml b/res/drawable/ic_microphone_spoofing.xml new file mode 100644 index 00000000000..fc51d0a4da2 --- /dev/null +++ b/res/drawable/ic_microphone_spoofing.xml @@ -0,0 +1,11 @@ + + + + diff --git a/res/values/strings.xml b/res/values/strings.xml index 4d95e4ae2f5..c61c6206027 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -235,6 +235,8 @@ Storage Scopes Contact Scopes + + Microphone Spoofing Install