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

Commit de69b4a1 authored by Shashwat Razdan's avatar Shashwat Razdan Committed by Android (Google) Code Review
Browse files

Merge "Addressing API feedback for ContextualSearchService" into main

parents dc5e60a5 c749692d
Loading
Loading
Loading
Loading
+8 −1
Original line number Diff line number Diff line
@@ -2178,8 +2178,15 @@ package android.app.contentsuggestions {
package android.app.contextualsearch {
  @FlaggedApi("android.app.contextualsearch.flags.enable_service") public final class CallbackToken implements android.os.Parcelable {
    ctor public CallbackToken();
    method public int describeContents();
    method public void getContextualSearchState(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.app.contextualsearch.ContextualSearchState,java.lang.Throwable>);
    method public void writeToParcel(@NonNull android.os.Parcel, int);
    field @NonNull public static final android.os.Parcelable.Creator<android.app.contextualsearch.CallbackToken> CREATOR;
  }
  @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
+8 −0
Original line number Diff line number Diff line
@@ -803,6 +803,14 @@ package android.app.contentsuggestions {

}

package android.app.contextualsearch {

  @FlaggedApi("android.app.contextualsearch.flags.enable_service") public final class CallbackToken implements android.os.Parcelable {
    method @NonNull public android.os.IBinder getToken();
  }

}

package android.app.job {

  public class JobParameters implements android.os.Parcelable {
+159 −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 android.annotation.CallbackExecutor;
import android.annotation.FlaggedApi;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.app.contextualsearch.flags.Flags;
import android.content.Context;
import android.os.Binder;
import android.os.IBinder;
import android.os.OutcomeReceiver;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.ParcelableException;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.Log;

import androidx.annotation.NonNull;

import java.util.concurrent.Executor;

/**
 * Used to share a single use token with the contextual search handling activity via the launch
 * extras bundle.
 * The caller can then use this token to get {@link ContextualSearchState} by calling
 * {@link #getContextualSearchState}.
 *
 * @hide
 */
@FlaggedApi(Flags.FLAG_ENABLE_SERVICE)
@SystemApi
public final class CallbackToken implements Parcelable {
    private static final boolean DEBUG = true;
    private static final String TAG = CallbackToken.class.getSimpleName();
    private final IBinder mToken;

    private boolean mTokenUsed = false;

    public CallbackToken() {
        mToken = new Binder();
    }

    private CallbackToken(Parcel in) {
        mToken = in.readStrongBinder();
    }

    /**
     * Returns the {@link ContextualSearchState} to the handler via the provided callback. The
     * method can only be invoked to provide the {@link OutcomeReceiver} once and all subsequent
     * invocations of this method will result in {@link OutcomeReceiver#onError} being called with
     * an {@link IllegalAccessException}.
     *
     * @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 @CallbackExecutor Executor executor,
            @NonNull OutcomeReceiver<ContextualSearchState, Throwable> callback) {
        if (DEBUG) Log.d(TAG, "getContextualSearchState for token:" + mToken);
        if (mTokenUsed) {
            callback.onError(new IllegalAccessException("Token already used."));
        }
        mTokenUsed = true;
        try {
            // Get the service from the system server.
            IBinder b = ServiceManager.getService(Context.CONTEXTUAL_SEARCH_SERVICE);
            IContextualSearchManager service = IContextualSearchManager.Stub.asInterface(b);
            final CallbackWrapper wrapper = new CallbackWrapper(executor, callback);
            // If the service is not null, hand over the call to the service.
            if (service != null) {
                service.getContextualSearchState(mToken, wrapper);
            } else {
                Log.w(TAG, "Failed to getContextualSearchState. Service null.");
            }
        } catch (RemoteException e) {
            if (DEBUG) Log.d(TAG, "Failed to call getContextualSearchState", e);
            e.rethrowFromSystemServer();
        }
    }

    /**
     * Return the token necessary for validating the caller of {@link #getContextualSearchState}.
     *
     * @hide
     */
    @TestApi
    @NonNull
    public IBinder getToken() {
        return mToken;
    }

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

    @Override
    public void writeToParcel(@NonNull Parcel dest, int flags) {
        dest.writeStrongBinder(mToken);
    }

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

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

    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));
            });
        }
    }
}
+3 −67
Original line number Diff line number Diff line
@@ -18,37 +18,26 @@ 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>
 * This class lets a caller start contextual search by calling {@link #startContextualSearch}
 * method.
 *
 * @hide
 */
@@ -92,7 +81,7 @@ public class ContextualSearchManager {

    /**
     * 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.
     * This token is needed to invoke {@link CallbackToken#getContextualSearchState} method.
     * Only supposed to be used with ACTON_LAUNCH_CONTEXTUAL_SEARCH.
     */
    public static final String EXTRA_TOKEN = "android.app.contextualsearch.extra.TOKEN";
@@ -174,57 +163,4 @@ public class ContextualSearchManager {
            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));
            });
        }
    }
}
+10 −0
Original line number Diff line number Diff line
@@ -32,6 +32,10 @@ import androidx.annotation.NonNull;
 * {@link ContextualSearchState} contains additional data a contextual search handler can request
 * via {@link ContextualSearchManager#getContextualSearchState} method.
 *
 * It provides the caller of {@link ContextualSearchManager#getContextualSearchState} with an
 * {@link AssistStructure}, {@link AssistContent} and a {@link Bundle} extras containing any
 * relevant data added by th system server. When invoked via the Launcher, this bundle is empty.
 *
 * @hide
 */
@FlaggedApi(Flags.FLAG_ENABLE_SERVICE)
@@ -41,6 +45,12 @@ public final class ContextualSearchState implements Parcelable {
    private final @Nullable AssistStructure mStructure;
    private final @Nullable AssistContent mContent;

    /**
     * {@link ContextualSearchState} contains non-essential data which can be requested by the
     * Contextual Search activity.
     * The activity can request an instance of {@link ContextualSearchState} by calling
     * {@link CallbackToken#getContextualSearchState} and passing a valid token as an argument.
     */
    public ContextualSearchState(@Nullable AssistStructure structure,
            @Nullable AssistContent content, @NonNull Bundle extras) {
        mStructure = structure;
Loading