Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 398625ba authored by Andy Wickham's avatar Andy Wickham Committed by Android (Google) Code Review
Browse files

Merge "Add ContextualSearchConfig to add display id, etc." into main

parents 50b767c1 272de368
Loading
Loading
Loading
Loading
+21 −2
Original line number Diff line number Diff line
@@ -2288,10 +2288,29 @@ package android.app.contextualsearch {
    field @NonNull public static final android.os.Parcelable.Creator<android.app.contextualsearch.CallbackToken> CREATOR;
  }
  @FlaggedApi("android.app.contextualsearch.flags.config_parameters") public final class ContextualSearchConfig implements android.os.Parcelable {
    method public int describeContents();
    method public int getDisplayId();
    method @NonNull public android.os.Bundle getIntentExtras();
    method @Nullable public android.graphics.Rect getSourceBounds();
    method public void writeToParcel(@NonNull android.os.Parcel, int);
    field @NonNull public static final android.os.Parcelable.Creator<android.app.contextualsearch.ContextualSearchConfig> CREATOR;
  }
  public static final class ContextualSearchConfig.Builder {
    ctor public ContextualSearchConfig.Builder();
    ctor public ContextualSearchConfig.Builder(@NonNull android.app.contextualsearch.ContextualSearchConfig);
    method @NonNull public android.app.contextualsearch.ContextualSearchConfig build();
    method @NonNull public android.app.contextualsearch.ContextualSearchConfig.Builder setDisplayId(int);
    method @NonNull public android.app.contextualsearch.ContextualSearchConfig.Builder setIntentExtras(@Nullable android.os.Bundle);
    method @NonNull public android.app.contextualsearch.ContextualSearchConfig.Builder setSourceBounds(@Nullable android.graphics.Rect);
  }
  public final class ContextualSearchManager {
    method @FlaggedApi("android.app.contextualsearch.flags.self_invocation") public boolean isContextualSearchAvailable();
    method @FlaggedApi("android.app.contextualsearch.flags.config_parameters") public boolean isContextualSearchAvailable();
    method @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXTUAL_SEARCH) public void startContextualSearch(int);
    method @FlaggedApi("android.app.contextualsearch.flags.self_invocation") public void startContextualSearch();
    method @FlaggedApi("android.app.contextualsearch.flags.config_parameters") @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXTUAL_SEARCH) public void startContextualSearch(int, @Nullable android.app.contextualsearch.ContextualSearchConfig);
    method @FlaggedApi("android.app.contextualsearch.flags.config_parameters") public void startContextualSearch(@NonNull android.app.Activity, @Nullable android.app.contextualsearch.ContextualSearchConfig);
    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
    field public static final int ENTRYPOINT_LONG_PRESS_META = 10; // 0xa
+208 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 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 android.app.contextualsearch;

import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.app.contextualsearch.flags.Flags;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.view.Display;

import java.util.Objects;

/**
 * Configuration for Contextual Search invocations. Typically the parameters added here are passed
 * to the Contextual Search provider app as specified by the device configuration.
 *
 * @hide
 */
@FlaggedApi(Flags.FLAG_CONFIG_PARAMETERS)
@SystemApi
public final class ContextualSearchConfig implements Parcelable {

    private final int mDisplayId;
    @Nullable private final Rect mSourceBounds;
    @NonNull private final Bundle mIntentExtras;

    public static final @NonNull Creator<ContextualSearchConfig> CREATOR =
            new Creator<>() {
                @Override
                public ContextualSearchConfig createFromParcel(@NonNull Parcel in) {
                    return new ContextualSearchConfig(in);
                }

                @Override
                public ContextualSearchConfig[] newArray(int size) {
                    return new ContextualSearchConfig[size];
                }
            };

    ContextualSearchConfig(@NonNull Parcel in) {
        mDisplayId = in.readInt();
        mSourceBounds = in.readTypedObject(Rect.CREATOR);
        mIntentExtras = Objects.requireNonNull(
                in.readBundle(ContextualSearchConfig.class.getClassLoader()));
    }

    @Override
    public void writeToParcel(@NonNull Parcel dest, int flags) {
        dest.writeInt(mDisplayId);
        dest.writeTypedObject(mSourceBounds, flags);
        dest.writeBundle(mIntentExtras);
    }

    @Override
    public int describeContents() {
        return 0;
    }

    private ContextualSearchConfig(@NonNull Builder builder) {
        mDisplayId = builder.mDisplayId;
        mSourceBounds = builder.mSourceBounds;
        mIntentExtras = builder.mIntentExtras;
    }

    /**
     * @return The ID of the display where the search was triggered. This determines where the
     *         screenshot is taken and displayed for user interaction. If the display ID is invalid,
     *         the invocation will fail silently. If not specified, the system will use
     *         {@link Display#DEFAULT_DISPLAY}, or the Activity's display if launched from an
     *         Activity.
     */
    public int getDisplayId() {
        return mDisplayId;
    }

    /**
     * @return The bounds of the source element that triggered the search, in screen coordinates.
     *         Can be null if not available.
     */
    @Nullable
    public Rect getSourceBounds() {
        return mSourceBounds == null ? null : new Rect(mSourceBounds);
    }

    /**
     * @return Extras to be added to the Intent sent to the Contextual Search app. These will be
     *         merged with any other extras added to the Intent by ContextualSearchManagerService.
     */
    @NonNull
    public Bundle getIntentExtras() {
        return new Bundle(mIntentExtras);
    }

    @Override
    public String toString() {
        return "ContextualSearchConfig{"
            + "mDisplayId=" + mDisplayId + ", "
            + "mSourceBounds=" + mSourceBounds + ", "
            + "mIntentExtras=" + mIntentExtras
            + '}';
    }

    /**
     * Builder to create a {@link ContextualSearchConfig}.
     */
    public static final class Builder {

        private int mDisplayId;
        @Nullable private Rect mSourceBounds;
        @NonNull private final Bundle mIntentExtras;

        /**
         * Creates a new Builder with default values.
         */
        public Builder() {
            mDisplayId = Display.INVALID_DISPLAY;
            mSourceBounds = null;
            mIntentExtras = new Bundle();
        }

        /**
         * Creates a new builder and initializes it with the values from the given
         * {@link ContextualSearchConfig}.
         *
         * @param config The config to copy values from.
         */
        public Builder(@NonNull ContextualSearchConfig config) {
            mDisplayId = config.getDisplayId();
            mSourceBounds = config.getSourceBounds();
            mIntentExtras = config.getIntentExtras();
        }

        /**
         * Sets the display ID for the contextual search invocation.
         *
         * @param displayId The ID of the display where the search was triggered. This determines
         *                  where the screenshot is taken and displayed for user interaction. If the
         *                  display ID is invalid, the invocation will fail silently. If not
         *                  specified, the system will use {@link Display#DEFAULT_DISPLAY}, or the
         *                  Activity's display if launched from an Activity.
         * @return This Builder object to allow for chaining of calls.
         */
        @NonNull
        public Builder setDisplayId(int displayId) {
            mDisplayId = displayId;
            return this;
        }

        /**
         * Sets the source bounds for the contextual search invocation.
         *
         * @param sourceBounds The bounds of the source element that triggered the search, in screen
         *                     coordinates. Can be null if not available.
         * @return This Builder object to allow for chaining of calls.
         */
        @NonNull
        public Builder setSourceBounds(@Nullable Rect sourceBounds) {
            mSourceBounds = sourceBounds == null ? null : new Rect(sourceBounds);
            return this;
        }

        /**
         * Sets any additional extras to be added to the intent sent to the Contextual Search app.
         *
         * @param intentExtras This will be merged with any other extras added to the intent by
         *                     ContextualSearchManagerService. To avoid having your extras
         *                     overwritten, prefix the keys with an agreed package name.
         * @return This Builder object to allow for chaining of calls.
         */
        @NonNull
        public Builder setIntentExtras(@Nullable Bundle intentExtras) {
            mIntentExtras.clear();
            if (intentExtras != null) {
                mIntentExtras.putAll(intentExtras);
            }
            return this;
        }

        /**
         * Builds the {@link ContextualSearchConfig} instance.
         *
         * @return The built {@link ContextualSearchConfig} object.
         */
        @NonNull
        public ContextualSearchConfig build() {
            return new ContextualSearchConfig(this);
        }
    }
}
+77 −22
Original line number Diff line number Diff line
@@ -20,8 +20,11 @@ import static android.Manifest.permission.ACCESS_CONTEXTUAL_SEARCH;

import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.app.Activity;
import android.app.contextualsearch.flags.Flags;
import android.content.Context;
import android.os.IBinder;
@@ -29,19 +32,20 @@ import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
import android.util.Log;
import android.view.Display;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;

/**
 * {@link ContextualSearchManager} is a system service to facilitate contextual search experience on
 * configured Android devices.
 * <p>
 * This class lets a caller start contextual search by calling {@link #startContextualSearch}
 * method.
 * configured Android devices. This involves capturing screenshots that the Contextual Search system
 * app presents to the user for interaction, such as selecting content on the screenshot to get a
 * search result or take an action such as calling a phone number or translating text.
 *
 * @hide
 */
@@ -256,16 +260,18 @@ public final class ContextualSearchManager {
    }

    /**
     * Used to check whether contextual search is available on the device. This method should be
     * called before calling {@link #startContextualSearch()} or adding any UI related to it to
     * ensure that the device is configured to support contextual search.
     * Used to check whether contextual search is available on the device. If this method returns
     * {code false}, you should not add any UI related to this feature, nor call
     * {@link #startContextualSearch(Activity, ContextualSearchConfig)}. It's rare but possible that
     * the return value of this method will change in subsequent calls, e.g. if the Contextual
     * Search app is disabled or enabled by the user.
     *
     * @see #startContextualSearch()
     * @return true if contextual search is available on the device, false otherwise.
     * @see #startContextualSearch(Activity, ContextualSearchConfig)
     * @return {@code true} if contextual search is currently available, {@code false} otherwise
     *
     * @hide
     */
    @FlaggedApi(Flags.FLAG_SELF_INVOCATION)
    @FlaggedApi(Flags.FLAG_CONFIG_PARAMETERS)
    @SystemApi
    public boolean isContextualSearchAvailable() {
        if (DEBUG) Log.d(TAG, "isContextualSearchAvailable");
@@ -295,20 +301,60 @@ public final class ContextualSearchManager {
     * <p>This method will fail silently if Contextual Search is not available on the device.
     *
     * @param entrypoint the invocation entrypoint
     * @throws SecurityException if the caller does not have the {@link ACCESS_CONTEXTUAL_SEARCH}
     * permission.
     *
     * @hide
     */
    @RequiresPermission(ACCESS_CONTEXTUAL_SEARCH)
    @SystemApi
    public void startContextualSearch(@Entrypoint int entrypoint) {
        if (DEBUG) Log.d(TAG, "startContextualSearch; entrypoint: " + entrypoint);
        startContextualSearchInternal(entrypoint, null);
    }

    /**
     * Used to start contextual search for a given system entrypoint.
     * <p>
     *     When {@link #startContextualSearch} is called, the system server does the following:
     *     <ul>
     *         <li>Resolves the activity using the package name and intent filter. The package name
     *             is fetched from the config specified in ContextualSearchManagerService.
     *             The activity must have ACTION_LAUNCH_CONTEXTUAL_SEARCH specified in its manifest.
     *         <li>Puts the required extras in the launch intent, which may include a
     *         {@link android.media.projection.MediaProjection} session.
     *         <li>Launches the activity.
     *     </ul>
     * </p>
     *
     * <p>This method will fail silently if Contextual Search is not available on the device.
     *
     * @param entrypoint the invocation entrypoint
     * @param config the invocation configuration parameters. If {@code null}, default configuration
     *               will be applied, including launching on {@link Display#DEFAULT_DISPLAY}.
     *
     * @hide
     */
    @RequiresPermission(ACCESS_CONTEXTUAL_SEARCH)
    @FlaggedApi(Flags.FLAG_CONFIG_PARAMETERS)
    @SystemApi
    public void startContextualSearch(@Entrypoint int entrypoint,
            @Nullable ContextualSearchConfig config) {
        startContextualSearchInternal(entrypoint, config);
    }

    /**
     * Internal method to start contextual search with an entrypoint and optional config.
     */
    @RequiresPermission(ACCESS_CONTEXTUAL_SEARCH)
    private void startContextualSearchInternal(@Entrypoint int entrypoint,
            @Nullable ContextualSearchConfig config) {
        if (!VALID_ENTRYPOINT_VALUES.contains(entrypoint)) {
            throw new IllegalArgumentException("Invalid entrypoint: " + entrypoint);
        }
        if (DEBUG) Log.d(TAG, "startContextualSearch for entrypoint: " + entrypoint);
        if (DEBUG) {
            Log.d(TAG, "startContextualSearch; entrypoint: " + entrypoint + "; config: " + config);
        }
        try {
            mService.startContextualSearch(entrypoint);
            mService.startContextualSearch(entrypoint, config);
        } catch (RemoteException e) {
            if (DEBUG) Log.d(TAG, "Failed to startContextualSearch", e);
            e.rethrowFromSystemServer();
@@ -319,27 +365,36 @@ public final class ContextualSearchManager {
     * Used to start Contextual Search from within an app. This will send a screenshot to the
     * Contextual Search app designated by the device manufacturer. The user can then select content
     * on the screenshot to get a search result or take an action such as calling a phone number or
     * translating the text.
     * translating the text. Note that the screenshot will capture the full display and may include
     * content outside of your Activity, e.g. in split screen mode.
     *
     * <p>Prior to calling this method or showing any UI related to it, you should verify that
     * Contextual Search is available on the device by using {@link #isContextualSearchAvailable()}.
     * Otherwise, this method will fail silently.
     *
     * <p>This method should only be called from an app that has a foreground Activity.
     * <p>Note: The system will use the display ID of your activity unless a displayId is specified
     * in the config. This is strongly discouraged unless you have a specific reason to specify a
     * different display.
     *
     * @see #isContextualSearchAvailable()
     * @throws SecurityException if the caller does not have a foreground Activity.
     * @param activity your foreground Activity from which the search is started
     * @param config the invocation configuration parameters. If {@code null}, default configuration
     *               will be applied, including launching the search on the same display as your
     *               activity.
     * @throws SecurityException if the caller does not have a foreground Activity
     *
     * @hide
     */
    @FlaggedApi(Flags.FLAG_SELF_INVOCATION)
    @FlaggedApi(Flags.FLAG_CONFIG_PARAMETERS)
    @SystemApi
    public void startContextualSearch() {
        if (DEBUG) Log.d(TAG, "startContextualSearch from app");
    public void startContextualSearch(@NonNull Activity activity,
            @Nullable ContextualSearchConfig config) {
        Objects.requireNonNull(activity);
        if (DEBUG) Log.d(TAG, "startContextualSearchForActivity(" + activity + ", " + config + ")");
        try {
            mService.startContextualSearchForForegroundApp();
            mService.startContextualSearchForActivity(activity.getActivityToken(), config);
        } catch (RemoteException e) {
            if (DEBUG) Log.d(TAG, "Failed to startContextualSearch", e);
            if (DEBUG) Log.d(TAG, "Failed to startContextualSearchForActivity", e);
            e.rethrowFromSystemServer();
        }
    }
+5 −2
Original line number Diff line number Diff line
package android.app.contextualsearch;

import android.app.contextualsearch.IContextualSearchCallback;

parcelable ContextualSearchConfig;

/**
 * @hide
 */
interface IContextualSearchManager {
  boolean isContextualSearchAvailable();
  void startContextualSearchForForegroundApp();
  oneway void startContextualSearch(int entrypoint);
  void startContextualSearchForActivity(in IBinder activityToken, in ContextualSearchConfig config);
  oneway void startContextualSearch(int entrypoint, in ContextualSearchConfig config);
  oneway void getContextualSearchState(in IBinder token, in IContextualSearchCallback callback);
}
+17 −14
Original line number Diff line number Diff line
@@ -51,9 +51,12 @@ flag {
}

flag {
  name: "self_invocation"
    name: "config_parameters"
    namespace: "sysui_integrations"
  description: "Enable apps to self-invoke Contextual Search."
  bug: "368653769"
    description: "Allow invocation parameters to be passed to Contextual Search, including from apps."
    bug: "371552433"
    is_exported: true
    metadata {
        purpose: PURPOSE_BUGFIX
    }
}
Loading