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

Commit c749692d authored by Shashwat Razdan's avatar Shashwat Razdan
Browse files

Addressing API feedback for ContextualSearchService

Bug: 329899105
Bug: 309657924
Test: New tests added
Change-Id: I98ed5daf8146903dd9e5c76f795258c0ea1b5c20
parent 6ef76569
Loading
Loading
Loading
Loading
+8 −1
Original line number Diff line number Diff line
@@ -2179,8 +2179,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
@@ -802,6 +802,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