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

Commit 6ef76569 authored by Shashwat Razdan's avatar Shashwat Razdan
Browse files

ContextualSearch API initial implementation

Test: CTS
Bug: 309689654
Change-Id: I0b820180eca2dfe32287132b941a5719ad7e26ca
parent 84fbc6b6
Loading
Loading
Loading
Loading
+14 −0
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ aconfig_declarations_group {
        // !!! KEEP THIS LIST ALPHABETICAL !!!
        "aconfig_mediacodec_flags_java_lib",
        "android.adaptiveauth.flags-aconfig-java",
        "android.app.contextualsearch.flags-aconfig-java",
        "android.app.flags-aconfig-java",
        "android.app.ondeviceintelligence-aconfig-java",
        "android.app.smartspace.flags-aconfig-java",
@@ -960,6 +961,19 @@ java_aconfig_library {
    ],
}

// Contextual Search
aconfig_declarations {
    name: "android.app.contextualsearch.flags-aconfig",
    package: "android.app.contextualsearch.flags",
    srcs: ["core/java/android/app/contextualsearch/flags.aconfig"],
}

java_aconfig_library {
    name: "android.app.contextualsearch.flags-aconfig-java",
    aconfig_declarations: "android.app.contextualsearch.flags-aconfig",
    defaults: ["framework-minus-apex-aconfig-java-defaults"],
}

// Smartspace
aconfig_declarations {
    name: "android.app.smartspace.flags-aconfig",
+35 −0
Original line number Diff line number Diff line
@@ -7,6 +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_CONTEXT_HUB = "android.permission.ACCESS_CONTEXT_HUB";
    field public static final String ACCESS_DRM_CERTIFICATES = "android.permission.ACCESS_DRM_CERTIFICATES";
    field @Deprecated public static final String ACCESS_FM_RADIO = "android.permission.ACCESS_FM_RADIO";
@@ -2176,6 +2177,39 @@ package android.app.contentsuggestions {
}
package android.app.contextualsearch {
  @FlaggedApi("android.app.contextualsearch.flags.enable_service") public class ContextualSearchManager {
    method public void getContextualSearchState(@NonNull android.os.IBinder, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.app.contextualsearch.ContextualSearchState,java.lang.Throwable>);
    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
    field public static final int ENTRYPOINT_LONG_PRESS_META = 10; // 0xa
    field public static final int ENTRYPOINT_LONG_PRESS_NAV_HANDLE = 1; // 0x1
    field public static final int ENTRYPOINT_LONG_PRESS_OVERVIEW = 3; // 0x3
    field public static final int ENTRYPOINT_OVERVIEW_ACTION = 4; // 0x4
    field public static final int ENTRYPOINT_OVERVIEW_MENU = 5; // 0x5
    field public static final int ENTRYPOINT_SYSTEM_ACTION = 9; // 0x9
    field public static final String EXTRA_ENTRYPOINT = "android.app.contextualsearch.extra.ENTRYPOINT";
    field public static final String EXTRA_FLAG_SECURE_FOUND = "android.app.contextualsearch.extra.FLAG_SECURE_FOUND";
    field public static final String EXTRA_IS_MANAGED_PROFILE_VISIBLE = "android.app.contextualsearch.extra.IS_MANAGED_PROFILE_VISIBLE";
    field public static final String EXTRA_SCREENSHOT = "android.app.contextualsearch.extra.SCREENSHOT";
    field public static final String EXTRA_TOKEN = "android.app.contextualsearch.extra.TOKEN";
    field public static final String EXTRA_VISIBLE_PACKAGE_NAMES = "android.app.contextualsearch.extra.VISIBLE_PACKAGE_NAMES";
  }
  @FlaggedApi("android.app.contextualsearch.flags.enable_service") public final class ContextualSearchState implements android.os.Parcelable {
    ctor public ContextualSearchState(@Nullable android.app.assist.AssistStructure, @Nullable android.app.assist.AssistContent, @NonNull android.os.Bundle);
    method public int describeContents();
    method @Nullable public android.app.assist.AssistContent getContent();
    method @NonNull public android.os.Bundle getExtras();
    method @Nullable public android.app.assist.AssistStructure getStructure();
    method public void writeToParcel(@NonNull android.os.Parcel, int);
    field @NonNull public static final android.os.Parcelable.Creator<android.app.contextualsearch.ContextualSearchState> CREATOR;
  }
}
package android.app.job {
  public abstract class JobScheduler {
@@ -3758,6 +3792,7 @@ package android.content {
    field public static final String CLOUDSEARCH_SERVICE = "cloudsearch";
    field public static final String CONTENT_SUGGESTIONS_SERVICE = "content_suggestions";
    field public static final String CONTEXTHUB_SERVICE = "contexthub";
    field @FlaggedApi("android.app.contextualsearch.flags.enable_service") public static final String CONTEXTUAL_SEARCH_SERVICE = "contextual_search";
    field @FlaggedApi("android.permission.flags.enhanced_confirmation_mode_apis_enabled") public static final String ECM_ENHANCED_CONFIRMATION_SERVICE = "ecm_enhanced_confirmation";
    field public static final String ETHERNET_SERVICE = "ethernet";
    field public static final String EUICC_CARD_SERVICE = "euicc_card";
+11 −0
Original line number Diff line number Diff line
@@ -32,6 +32,7 @@ import android.app.appsearch.AppSearchManagerFrameworkInitializer;
import android.app.blob.BlobStoreManagerFrameworkInitializer;
import android.app.contentsuggestions.ContentSuggestionsManager;
import android.app.contentsuggestions.IContentSuggestionsManager;
import android.app.contextualsearch.ContextualSearchManager;
import android.app.ecm.EnhancedConfirmationFrameworkInitializer;
import android.app.job.JobSchedulerFrameworkInitializer;
import android.app.people.PeopleManager;
@@ -1280,6 +1281,16 @@ public final class SystemServiceRegistry {
                }
            });

        registerService(Context.CONTEXTUAL_SEARCH_SERVICE, ContextualSearchManager.class,
                new CachedServiceFetcher<>() {
                    @Override
                    public ContextualSearchManager createService(ContextImpl ctx)
                            throws ServiceNotFoundException {
                        IBinder b = ServiceManager.getService(Context.CONTEXTUAL_SEARCH_SERVICE);
                        return b == null ? null : new ContextualSearchManager();
                    }
                });

        registerService(Context.APP_PREDICTION_SERVICE, AppPredictionManager.class,
                new CachedServiceFetcher<AppPredictionManager>() {
            @Override
+230 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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 static android.Manifest.permission.ACCESS_CONTEXTUAL_SEARCH;

import android.annotation.CallbackExecutor;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.app.contextualsearch.flags.Flags;
import android.content.Context;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.os.OutcomeReceiver;
import android.os.ParcelableException;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.Log;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.concurrent.Executor;

/**
 * {@link ContextualSearchManager} is a system service to facilitate contextual search experience on
 * configured Android devices.
 * <p>
 * This class lets
 * <ul>
 *   <li> a caller start contextual search by calling {@link #startContextualSearch} method.
 *   <li> a handler request {@link ContextualSearchState} by calling the
 *   {@link #getContextualSearchState} method.
 * </ul>
 *
 * @hide
 */
@SystemApi
@FlaggedApi(Flags.FLAG_ENABLE_SERVICE)
public class ContextualSearchManager {

    /**
     * Key to get the entrypoint from the extras of the activity launched by contextual search.
     * Only supposed to be used with ACTON_LAUNCH_CONTEXTUAL_SEARCH.
     */
    public static final String EXTRA_ENTRYPOINT =
            "android.app.contextualsearch.extra.ENTRYPOINT";
    /**
     * Key to get the flag_secure value from the extras of the activity launched by contextual
     * search. The value will be true if flag_secure is found in any of the visible activities.
     * Only supposed to be used with ACTON_LAUNCH_CONTEXTUAL_SEARCH.
     */
    public static final String EXTRA_FLAG_SECURE_FOUND =
            "android.app.contextualsearch.extra.FLAG_SECURE_FOUND";
    /**
     * Key to get the screenshot from the extras of the activity launched by contextual search.
     * Only supposed to be used with ACTON_LAUNCH_CONTEXTUAL_SEARCH.
     */
    public static final String EXTRA_SCREENSHOT =
            "android.app.contextualsearch.extra.SCREENSHOT";
    /**
     * Key to check whether managed profile is visible from the extras of the activity launched by
     * contextual search. The value will be true if any one of the visible apps is managed.
     * Only supposed to be used with ACTON_LAUNCH_CONTEXTUAL_SEARCH.
     */
    public static final String EXTRA_IS_MANAGED_PROFILE_VISIBLE =
            "android.app.contextualsearch.extra.IS_MANAGED_PROFILE_VISIBLE";
    /**
     * Key to get the list of visible packages from the extras of the activity launched by
     * contextual search.
     * Only supposed to be used with ACTON_LAUNCH_CONTEXTUAL_SEARCH.
     */
    public static final String EXTRA_VISIBLE_PACKAGE_NAMES =
            "android.app.contextualsearch.extra.VISIBLE_PACKAGE_NAMES";

    /**
     * Key to get the binder token from the extras of the activity launched by contextual search.
     * This token is needed to invoke {@link #getContextualSearchState} method.
     * Only supposed to be used with ACTON_LAUNCH_CONTEXTUAL_SEARCH.
     */
    public static final String EXTRA_TOKEN = "android.app.contextualsearch.extra.TOKEN";
    /**
     * Intent action for contextual search invocation. The app providing the contextual search
     * 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.
     */
    public static final String ACTION_LAUNCH_CONTEXTUAL_SEARCH =
            "android.app.contextualsearch.action.LAUNCH_CONTEXTUAL_SEARCH";

    /** Entrypoint to be used when a user long presses on the nav handle. */
    public static final int ENTRYPOINT_LONG_PRESS_NAV_HANDLE = 1;
    /** Entrypoint to be used when a user long presses on the home button. */
    public static final int ENTRYPOINT_LONG_PRESS_HOME = 2;
    /** Entrypoint to be used when a user long presses on the overview button. */
    public static final int ENTRYPOINT_LONG_PRESS_OVERVIEW = 3;
    /** Entrypoint to be used when a user presses the action button in overview. */
    public static final int ENTRYPOINT_OVERVIEW_ACTION = 4;
    /** Entrypoint to be used when a user presses the context menu button in overview. */
    public static final int ENTRYPOINT_OVERVIEW_MENU = 5;
    /** Entrypoint to be used by system actions like TalkBack, Accessibility etc. */
    public static final int ENTRYPOINT_SYSTEM_ACTION = 9;
    /** Entrypoint to be used when a user long presses on the meta key. */
    public static final int ENTRYPOINT_LONG_PRESS_META = 10;
    /**
     * The {@link Entrypoint} annotation is used to standardize the entrypoints supported by
     * {@link #startContextualSearch} method.
     *
     * @hide
     */
    @IntDef(prefix = {"ENTRYPOINT_"}, value = {
            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
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface Entrypoint {
    }
    private static final String TAG = ContextualSearchManager.class.getSimpleName();
    private static final boolean DEBUG = false;

    private final IContextualSearchManager mService;

    /** @hide */
    public ContextualSearchManager() {
        if (DEBUG) Log.d(TAG, "ContextualSearchManager created");
        IBinder b = ServiceManager.getService(Context.CONTEXTUAL_SEARCH_SERVICE);
        mService = IContextualSearchManager.Stub.asInterface(b);
    }

    /**
     * Used to start contextual search.
     * <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.
     *         <li>Launches the activity.
     *     </ul>
     * </p>
     *
     * @param entrypoint the invocation entrypoint
     */
    @RequiresPermission(ACCESS_CONTEXTUAL_SEARCH)
    public void startContextualSearch(@Entrypoint int entrypoint) {
        if (DEBUG) Log.d(TAG, "startContextualSearch for entrypoint: " + entrypoint);
        try {
            mService.startContextualSearch(entrypoint);
        } catch (RemoteException e) {
            if (DEBUG) Log.d(TAG, "Failed to startContextualSearch", e);
            e.rethrowFromSystemServer();
        }
    }

    /**
     * Returns the {@link ContextualSearchState} to the handler via the provided callback.
     *
     * @param token The caller is expected to get the token from the launch extras of the handling
     *              activity using {@link Bundle#getIBinder} with {@link #EXTRA_TOKEN} key.
     *              <br>
     *              <b>Note</b> This token is for one time use only. Subsequent uses will invoke
     *              callback's {@link OutcomeReceiver#onError}.
     * @param executor The executor which will be used to invoke the callback.
     * @param callback The callback which will be used to return {@link ContextualSearchState}
     *                 if/when it is available via {@link OutcomeReceiver#onResult}. It will also be
     *                 used to return errors via {@link OutcomeReceiver#onError}.
     */
    public void getContextualSearchState(@NonNull IBinder token,
            @NonNull @CallbackExecutor Executor executor,
            @NonNull OutcomeReceiver<ContextualSearchState, Throwable> callback) {
        if (DEBUG) Log.d(TAG, "getContextualSearchState for token:" + token);
        try {
            final CallbackWrapper wrapper = new CallbackWrapper(executor, callback);
            mService.getContextualSearchState(token, wrapper);
        } catch (RemoteException e) {
            if (DEBUG) Log.d(TAG, "Failed to getContextualSearchState", e);
            e.rethrowFromSystemServer();
        }
    }

    private static class CallbackWrapper extends IContextualSearchCallback.Stub {
        private final OutcomeReceiver<ContextualSearchState, Throwable> mCallback;
        private final Executor mExecutor;

        CallbackWrapper(@NonNull Executor callbackExecutor,
                        @NonNull OutcomeReceiver<ContextualSearchState, Throwable> callback) {
            mCallback = callback;
            mExecutor = callbackExecutor;
        }

        @Override
        public void onResult(ContextualSearchState state) {
            Binder.withCleanCallingIdentity(() -> {
                if (DEBUG) Log.d(TAG, "onResult state:" + state);
                mExecutor.execute(() -> mCallback.onResult(state));
            });
        }

        @Override
        public void onError(ParcelableException error) {
            Binder.withCleanCallingIdentity(() -> {
                if (DEBUG) Log.w(TAG, "onError", error);
                mExecutor.execute(() -> mCallback.onError(error));
            });
        }
    }
}
+19 −0
Original line number Diff line number Diff line
/**
 * Copyright (c) 2021, 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;

parcelable ContextualSearchState;
 No newline at end of file
Loading