summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Andy Wickham <awickham@google.com> 2025-01-31 12:45:58 -0800
committer Andy Wickham <awickham@google.com> 2025-02-06 21:42:56 +0000
commitdfcdb093c7452d24cbb334503927f76707bbe780 (patch)
tree72789ac4abe6cf9c53aa1be0f67e1bea0c9c12f6
parent073700f2107345bcd922843fbb7ee168c0d407d4 (diff)
Adds a public Contextual Search invocation method.
- Adds the new startContextualSearch() method. - Makes ContextualSearchManager non-hidden to expose that method. - The above clas and method are behind FLAG_SELF_INVOCATION. - This method requires the caller to have a foreground activity. - A corresponding startContextualSearchForForegroundApp() method is added to the service, which is is non-oneway to propagate SecurityException for background calls. Demo with SupportApp: https://drive.google.com/file/d/1Il-jWGosepboB9462yjn_vGA8hzDXaR2/view?usp=drive_link&resourcekey=0-dvuGjbwlML7UOST3IjJuDw Bug: 368653769 Test: Build and flash, invoke with SupportApp Flag: android.app.contextualsearch.flags.self_invocation Change-Id: I296ce39cb6349e41dc39a20820e5e445860e5c8a
-rw-r--r--core/api/current.txt8
-rw-r--r--core/api/system-current.txt4
-rw-r--r--core/java/android/app/contextualsearch/ContextualSearchManager.java121
-rw-r--r--core/java/android/app/contextualsearch/IContextualSearchManager.aidl7
-rw-r--r--core/java/android/app/contextualsearch/flags.aconfig8
-rw-r--r--core/res/AndroidManifest.xml10
-rw-r--r--services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java77
7 files changed, 191 insertions, 44 deletions
diff --git a/core/api/current.txt b/core/api/current.txt
index dd606774b770..666d0aa3954a 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -9187,6 +9187,14 @@ package android.app.blob {
}
+package android.app.contextualsearch {
+
+ @FlaggedApi("android.app.contextualsearch.flags.self_invocation") public final class ContextualSearchManager {
+ method @FlaggedApi("android.app.contextualsearch.flags.self_invocation") public void startContextualSearch();
+ }
+
+}
+
package android.app.jank {
@FlaggedApi("android.app.jank.detailed_app_jank_metrics_api") public final class AppJankStats {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index ab824119d643..354c16b889d8 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -7,7 +7,7 @@ package android {
field public static final String ACCESS_BROADCAST_RADIO = "android.permission.ACCESS_BROADCAST_RADIO";
field public static final String ACCESS_BROADCAST_RESPONSE_STATS = "android.permission.ACCESS_BROADCAST_RESPONSE_STATS";
field public static final String ACCESS_CACHE_FILESYSTEM = "android.permission.ACCESS_CACHE_FILESYSTEM";
- field @FlaggedApi("android.app.contextualsearch.flags.enable_service") public static final String ACCESS_CONTEXTUAL_SEARCH = "android.permission.ACCESS_CONTEXTUAL_SEARCH";
+ field public static final String ACCESS_CONTEXTUAL_SEARCH = "android.permission.ACCESS_CONTEXTUAL_SEARCH";
field public static final String ACCESS_CONTEXT_HUB = "android.permission.ACCESS_CONTEXT_HUB";
field public static final String ACCESS_DRM_CERTIFICATES = "android.permission.ACCESS_DRM_CERTIFICATES";
field @FlaggedApi("android.permission.flags.fine_power_monitor_permission") public static final String ACCESS_FINE_POWER_MONITORS = "android.permission.ACCESS_FINE_POWER_MONITORS";
@@ -2231,7 +2231,7 @@ package android.app.contextualsearch {
field @NonNull public static final android.os.Parcelable.Creator<android.app.contextualsearch.CallbackToken> CREATOR;
}
- public final class ContextualSearchManager {
+ @FlaggedApi("android.app.contextualsearch.flags.self_invocation") public final class ContextualSearchManager {
method @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXTUAL_SEARCH) public void startContextualSearch(int);
field public static final String ACTION_LAUNCH_CONTEXTUAL_SEARCH = "android.app.contextualsearch.action.LAUNCH_CONTEXTUAL_SEARCH";
field public static final int ENTRYPOINT_LONG_PRESS_HOME = 2; // 0x2
diff --git a/core/java/android/app/contextualsearch/ContextualSearchManager.java b/core/java/android/app/contextualsearch/ContextualSearchManager.java
index 2ce431dcb32d..4e5fa6bac951 100644
--- a/core/java/android/app/contextualsearch/ContextualSearchManager.java
+++ b/core/java/android/app/contextualsearch/ContextualSearchManager.java
@@ -32,6 +32,9 @@ import android.util.Log;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
/**
* {@link ContextualSearchManager} is a system service to facilitate contextual search experience on
@@ -39,10 +42,8 @@ import java.lang.annotation.RetentionPolicy;
* <p>
* This class lets a caller start contextual search by calling {@link #startContextualSearch}
* method.
- *
- * @hide
*/
-@SystemApi
+@FlaggedApi(Flags.FLAG_SELF_INVOCATION)
public final class ContextualSearchManager {
/**
@@ -50,7 +51,9 @@ public final class ContextualSearchManager {
* Only supposed to be used with ACTION_LAUNCH_CONTEXTUAL_SEARCH.
*
* @see #ACTION_LAUNCH_CONTEXTUAL_SEARCH
+ * @hide
*/
+ @SystemApi
public static final String EXTRA_ENTRYPOINT =
"android.app.contextualsearch.extra.ENTRYPOINT";
@@ -60,7 +63,9 @@ public final class ContextualSearchManager {
* Only supposed to be used with ACTION_LAUNCH_CONTEXTUAL_SEARCH.
*
* @see #ACTION_LAUNCH_CONTEXTUAL_SEARCH
+ * @hide
*/
+ @SystemApi
public static final String EXTRA_FLAG_SECURE_FOUND =
"android.app.contextualsearch.extra.FLAG_SECURE_FOUND";
@@ -69,7 +74,9 @@ public final class ContextualSearchManager {
* Only supposed to be used with ACTION_LAUNCH_CONTEXTUAL_SEARCH.
*
* @see #ACTION_LAUNCH_CONTEXTUAL_SEARCH
+ * @hide
*/
+ @SystemApi
public static final String EXTRA_SCREENSHOT =
"android.app.contextualsearch.extra.SCREENSHOT";
@@ -79,7 +86,9 @@ public final class ContextualSearchManager {
* Only supposed to be used with ACTION_LAUNCH_CONTEXTUAL_SEARCH.
*
* @see #ACTION_LAUNCH_CONTEXTUAL_SEARCH
+ * @hide
*/
+ @SystemApi
public static final String EXTRA_IS_MANAGED_PROFILE_VISIBLE =
"android.app.contextualsearch.extra.IS_MANAGED_PROFILE_VISIBLE";
@@ -89,7 +98,9 @@ public final class ContextualSearchManager {
* Only supposed to be used with ACTION_LAUNCH_CONTEXTUAL_SEARCH.
*
* @see #ACTION_LAUNCH_CONTEXTUAL_SEARCH
+ * @hide
*/
+ @SystemApi
public static final String EXTRA_VISIBLE_PACKAGE_NAMES =
"android.app.contextualsearch.extra.VISIBLE_PACKAGE_NAMES";
@@ -98,10 +109,9 @@ public final class ContextualSearchManager {
* {@link SystemClock#uptimeMillis()}.
* Only supposed to be used with ACTION_LAUNCH_CONTEXTUAL_SEARCH.
*
- * @see #ACTION_LAUNCH_CONTEXTUAL_SEARCH
- *
* TODO: un-hide in W
*
+ * @see #ACTION_LAUNCH_CONTEXTUAL_SEARCH
* @hide
*/
public static final String EXTRA_INVOCATION_TIME_MS =
@@ -113,7 +123,9 @@ public final class ContextualSearchManager {
* Only supposed to be used with ACTION_LAUNCH_CONTEXTUAL_SEARCH.
*
* @see #ACTION_LAUNCH_CONTEXTUAL_SEARCH
+ * @hide
*/
+ @SystemApi
public static final String EXTRA_TOKEN = "android.app.contextualsearch.extra.TOKEN";
/**
@@ -132,7 +144,10 @@ public final class ContextualSearchManager {
* experience must add this intent filter action to the activity it wants to be launched.
* <br>
* <b>Note</b> This activity must not be exported.
+ *
+ * @hide
*/
+ @SystemApi
public static final String ACTION_LAUNCH_CONTEXTUAL_SEARCH =
"android.app.contextualsearch.action.LAUNCH_CONTEXTUAL_SEARCH";
@@ -144,23 +159,63 @@ public final class ContextualSearchManager {
public static final String FEATURE_CONTEXTUAL_SEARCH =
"com.google.android.feature.CONTEXTUAL_SEARCH";
- /** Entrypoint to be used when a user long presses on the nav handle. */
+ /**
+ * Entrypoint to be used when a user long presses on the nav handle.
+ *
+ * @hide
+ */
+ @SystemApi
public static final int ENTRYPOINT_LONG_PRESS_NAV_HANDLE = 1;
- /** Entrypoint to be used when a user long presses on the home button. */
+
+ /** Entrypoint to be used when a user long presses on the home button.
+ *
+ * @hide
+ */
+ @SystemApi
public static final int ENTRYPOINT_LONG_PRESS_HOME = 2;
- /** Entrypoint to be used when a user long presses on the overview button. */
+
+ /** Entrypoint to be used when a user long presses on the overview button.
+ *
+ * @hide
+ */
+ @SystemApi
public static final int ENTRYPOINT_LONG_PRESS_OVERVIEW = 3;
- /** Entrypoint to be used when a user presses the action button in overview. */
+
+ /**
+ * Entrypoint to be used when a user presses the action button in overview.
+ *
+ * @hide
+ */
+ @SystemApi
public static final int ENTRYPOINT_OVERVIEW_ACTION = 4;
- /** Entrypoint to be used when a user presses the context menu button in overview. */
+
+ /**
+ * Entrypoint to be used when a user presses the context menu button in overview.
+ *
+ * @hide
+ */
+ @SystemApi
public static final int ENTRYPOINT_OVERVIEW_MENU = 5;
- /** Entrypoint to be used by system actions like TalkBack, Accessibility etc. */
+
+ /**
+ * Entrypoint to be used by system actions like TalkBack, Accessibility etc.
+ *
+ * @hide
+ */
+ @SystemApi
public static final int ENTRYPOINT_SYSTEM_ACTION = 9;
- /** Entrypoint to be used when a user long presses on the meta key. */
+
+ /**
+ * Entrypoint to be used when a user long presses on the meta key.
+ *
+ * @hide
+ */
+ @SystemApi
public static final int ENTRYPOINT_LONG_PRESS_META = 10;
+
/**
* The {@link Entrypoint} annotation is used to standardize the entrypoints supported by
- * {@link #startContextualSearch} method.
+ * {@link #startContextualSearch(int entrypoint)} method.
*
* @hide
*/
@@ -174,8 +229,18 @@ public final class ContextualSearchManager {
ENTRYPOINT_LONG_PRESS_META
})
@Retention(RetentionPolicy.SOURCE)
- public @interface Entrypoint {
- }
+ public @interface Entrypoint {}
+
+ private static final Set<Integer> VALID_ENTRYPOINT_VALUES = new HashSet<>(Arrays.asList(
+ ENTRYPOINT_LONG_PRESS_NAV_HANDLE,
+ ENTRYPOINT_LONG_PRESS_HOME,
+ ENTRYPOINT_LONG_PRESS_OVERVIEW,
+ ENTRYPOINT_OVERVIEW_ACTION,
+ ENTRYPOINT_OVERVIEW_MENU,
+ ENTRYPOINT_SYSTEM_ACTION,
+ ENTRYPOINT_LONG_PRESS_META
+ ));
+
private static final String TAG = ContextualSearchManager.class.getSimpleName();
private static final boolean DEBUG = false;
@@ -189,7 +254,7 @@ public final class ContextualSearchManager {
}
/**
- * Used to start contextual search.
+ * Used to start contextual search for a given system entrypoint.
* <p>
* When {@link #startContextualSearch} is called, the system server does the following:
* <ul>
@@ -202,9 +267,15 @@ public final class ContextualSearchManager {
* </p>
*
* @param entrypoint the invocation entrypoint
+ *
+ * @hide
*/
@RequiresPermission(ACCESS_CONTEXTUAL_SEARCH)
+ @SystemApi
public void startContextualSearch(@Entrypoint int entrypoint) {
+ if (!VALID_ENTRYPOINT_VALUES.contains(entrypoint)) {
+ throw new IllegalArgumentException("Invalid entrypoint: " + entrypoint);
+ }
if (DEBUG) Log.d(TAG, "startContextualSearch for entrypoint: " + entrypoint);
try {
mService.startContextualSearch(entrypoint);
@@ -213,4 +284,22 @@ public final class ContextualSearchManager {
e.rethrowFromSystemServer();
}
}
+
+ /**
+ * Used to start contextual search from within an app.
+ *
+ * <p>System apps should use the available System APIs rather than this method.
+ *
+ * @throws SecurityException if the caller does not have a foreground Activity.
+ */
+ @FlaggedApi(Flags.FLAG_SELF_INVOCATION)
+ public void startContextualSearch() {
+ if (DEBUG) Log.d(TAG, "startContextualSearch from app");
+ try {
+ mService.startContextualSearchForForegroundApp();
+ } catch (RemoteException e) {
+ if (DEBUG) Log.d(TAG, "Failed to startContextualSearch", e);
+ e.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/core/java/android/app/contextualsearch/IContextualSearchManager.aidl b/core/java/android/app/contextualsearch/IContextualSearchManager.aidl
index 9b0b8b775971..8789daab3afe 100644
--- a/core/java/android/app/contextualsearch/IContextualSearchManager.aidl
+++ b/core/java/android/app/contextualsearch/IContextualSearchManager.aidl
@@ -4,7 +4,8 @@ import android.app.contextualsearch.IContextualSearchCallback;
/**
* @hide
*/
-oneway interface IContextualSearchManager {
- void startContextualSearch(int entrypoint);
- void getContextualSearchState(in IBinder token, in IContextualSearchCallback callback);
+interface IContextualSearchManager {
+ void startContextualSearchForForegroundApp();
+ oneway void startContextualSearch(int entrypoint);
+ oneway void getContextualSearchState(in IBinder token, in IContextualSearchCallback callback);
}
diff --git a/core/java/android/app/contextualsearch/flags.aconfig b/core/java/android/app/contextualsearch/flags.aconfig
index d81ec1e8b883..bc1f7cea7fce 100644
--- a/core/java/android/app/contextualsearch/flags.aconfig
+++ b/core/java/android/app/contextualsearch/flags.aconfig
@@ -39,3 +39,11 @@ flag {
description: "Add audio playing status to the contextual search invocation intent."
bug: "372935419"
}
+
+flag {
+ name: "self_invocation"
+ namespace: "sysui_integrations"
+ description: "Enable apps to self-invoke Contextual Search."
+ bug: "368653769"
+ is_exported: true
+}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index c8ad7dae28d4..75492da69beb 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -7688,12 +7688,12 @@
<permission android:name="android.permission.ACCESS_SMARTSPACE"
android:protectionLevel="signature|privileged|development" />
- <!-- @SystemApi Allows an application to start a contextual search.
- @FlaggedApi("android.app.contextualsearch.flags.enable_service")
- @hide <p>Not for use by third-party applications.</p> -->
+ <!-- @SystemApi Allows a system application to start a contextual search.
+ Other applications can start a contextual search only if they have a
+ foreground activity.
+ @hide <p>Not for use by third-party applications.</p> -->
<permission android:name="android.permission.ACCESS_CONTEXTUAL_SEARCH"
- android:protectionLevel="signature|privileged"
- android:featureFlag="android.app.contextualsearch.flags.enable_service"/>
+ android:protectionLevel="signature|privileged" />
<!-- @SystemApi Allows an application to manage the wallpaper effects
generation service.
diff --git a/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java b/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java
index 89c9d690a82c..700a1624f7d4 100644
--- a/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java
+++ b/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java
@@ -34,6 +34,8 @@ import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_STRUC
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
+import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
import android.app.ActivityOptions;
import android.app.AppOpsManager;
import android.app.admin.DevicePolicyManagerInternal;
@@ -69,7 +71,6 @@ import android.provider.Settings;
import android.util.Log;
import android.util.Slog;
import android.view.IWindowManager;
-import android.window.ScreenCapture;
import android.window.ScreenCapture.ScreenshotHardwareBuffer;
import com.android.internal.R;
@@ -86,7 +87,6 @@ import java.io.FileDescriptor;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
-import java.util.Set;
public class ContextualSearchManagerService extends SystemService {
private static final String TAG = ContextualSearchManagerService.class.getSimpleName();
@@ -95,9 +95,20 @@ public class ContextualSearchManagerService extends SystemService {
private static final int MSG_INVALIDATE_TOKEN = 1;
private static final int MAX_TOKEN_VALID_DURATION_MS = 1_000 * 60 * 10; // 10 minutes
+ /**
+ * Below are internal entrypoints not supported by the
+ * {@link ContextualSearchManager#startContextualSearch(int entrypoint)} method.
+ *
+ * <p>These values should be negative to avoid conflicting with the system entrypoints.
+ */
+
+ /** Entrypoint to be used when a foreground app invokes Contextual Search. */
+ private static final int INTERNAL_ENTRYPOINT_APP = -1;
+
private static final boolean DEBUG = false;
private final Context mContext;
+ private final ActivityManagerInternal mActivityManagerInternal;
private final ActivityTaskManagerInternal mAtmInternal;
private final PackageManagerInternal mPackageManager;
private final WindowManagerInternal mWmInternal;
@@ -162,6 +173,8 @@ public class ContextualSearchManagerService extends SystemService {
super(context);
if (DEBUG) Log.d(TAG, "ContextualSearchManagerService created");
mContext = context;
+ mActivityManagerInternal = Objects.requireNonNull(
+ LocalServices.getService(ActivityManagerInternal.class));
mAtmInternal = Objects.requireNonNull(
LocalServices.getService(ActivityTaskManagerInternal.class));
mPackageManager = LocalServices.getService(PackageManagerInternal.class);
@@ -391,6 +404,20 @@ public class ContextualSearchManagerService extends SystemService {
}
}
+ private void enforceForegroundApp(@NonNull final String func) {
+ final int callingUid = Binder.getCallingUid();
+ final String callingPackage = mPackageManager.getNameForUid(Binder.getCallingUid());
+ if (mActivityManagerInternal.getUidProcessState(callingUid)
+ > ActivityManager.PROCESS_STATE_TOP) {
+ // The calling process must be displaying an activity in foreground to
+ // trigger contextual search.
+ String msg = "Permission Denial: Cannot call " + func + " from pid="
+ + Binder.getCallingPid() + ", uid=" + callingUid
+ + ", package=" + callingPackage + " without a foreground activity.";
+ throw new SecurityException(msg);
+ }
+ }
+
private void enforceOverridingPermission(@NonNull final String func) {
if (!(Binder.getCallingUid() == Process.SHELL_UID
|| Binder.getCallingUid() == Process.ROOT_UID
@@ -448,29 +475,43 @@ public class ContextualSearchManagerService extends SystemService {
}
@Override
+ public void startContextualSearchForForegroundApp() {
+ synchronized (this) {
+ if (DEBUG) {
+ Log.d(TAG, "Starting contextual search from: "
+ + mPackageManager.getNameForUid(Binder.getCallingUid()));
+ }
+ enforceForegroundApp("startContextualSearchForForegroundApp");
+ startContextualSearchInternal(INTERNAL_ENTRYPOINT_APP);
+ }
+ }
+
+ @Override
public void startContextualSearch(int entrypoint) {
synchronized (this) {
if (DEBUG) Log.d(TAG, "startContextualSearch entrypoint: " + entrypoint);
enforcePermission("startContextualSearch");
- final int callingUserId = Binder.getCallingUserHandle().getIdentifier();
-
- mAssistDataRequester.cancel();
- // Creates a new CallbackToken at mToken and an expiration handler.
- issueToken();
- // We get the launch intent with the system server's identity because the system
- // server has READ_FRAME_BUFFER permission to get the screenshot and because only
- // the system server can invoke non-exported activities.
- Binder.withCleanCallingIdentity(() -> {
- Intent launchIntent =
- getContextualSearchIntent(entrypoint, callingUserId, mToken);
- if (launchIntent != null) {
- int result = invokeContextualSearchIntent(launchIntent, callingUserId);
- if (DEBUG) Log.d(TAG, "Launch result: " + result);
- }
- });
+ startContextualSearchInternal(entrypoint);
}
}
+ private void startContextualSearchInternal(int entrypoint) {
+ final int callingUserId = Binder.getCallingUserHandle().getIdentifier();
+ mAssistDataRequester.cancel();
+ // Creates a new CallbackToken at mToken and an expiration handler.
+ issueToken();
+ // We get the launch intent with the system server's identity because the system
+ // server has READ_FRAME_BUFFER permission to get the screenshot and because only
+ // the system server can invoke non-exported activities.
+ Binder.withCleanCallingIdentity(() -> {
+ Intent launchIntent = getContextualSearchIntent(entrypoint, callingUserId, mToken);
+ if (launchIntent != null) {
+ int result = invokeContextualSearchIntent(launchIntent, callingUserId);
+ if (DEBUG) Log.d(TAG, "Launch result: " + result);
+ }
+ });
+ }
+
@Override
public void getContextualSearchState(
@NonNull IBinder token,