Ensure some calls are available for testing

Bug: 122694646
Test: atest CtsAppPredictionServiceTestCases:AppPredictionServiceTest

Change-Id: I4534eabf8ec6365cd49117f567e25eadf3f63407
diff --git a/core/java/android/app/prediction/AppPredictionContext.java b/core/java/android/app/prediction/AppPredictionContext.java
index 87ccb66..2fbe6e36 100644
--- a/core/java/android/app/prediction/AppPredictionContext.java
+++ b/core/java/android/app/prediction/AppPredictionContext.java
@@ -18,6 +18,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.content.Context;
 import android.os.Bundle;
 import android.os.Parcel;
@@ -29,6 +30,7 @@
  * @hide
  */
 @SystemApi
+@TestApi
 public final class AppPredictionContext implements Parcelable {
 
     private final int mPredictedTargetCount;
@@ -73,6 +75,17 @@
     }
 
     @Override
+    public boolean equals(Object o) {
+        if (o == this) return true;
+        if (!getClass().equals(o != null ? o.getClass() : null)) return false;
+
+        AppPredictionContext other = (AppPredictionContext) o;
+        return mPredictedTargetCount == other.mPredictedTargetCount
+                && mUiSurface.equals(other.mUiSurface)
+                && mPackageName.equals(other.mPackageName);
+    }
+
+    @Override
     public int describeContents() {
         return 0;
     }
@@ -104,6 +117,7 @@
      * @hide
      */
     @SystemApi
+    @TestApi
     public static final class Builder {
 
         @NonNull
@@ -116,8 +130,12 @@
         private Bundle mExtras;
 
         /**
+         * TODO(b/123591863): Add java docs
+         *
          * @hide
          */
+        @SystemApi
+        @TestApi
         public Builder(@NonNull Context context) {
             mPackageName = context.getPackageName();
         }
diff --git a/core/java/android/app/prediction/AppPredictionManager.java b/core/java/android/app/prediction/AppPredictionManager.java
index f8578d4..99f78f1 100644
--- a/core/java/android/app/prediction/AppPredictionManager.java
+++ b/core/java/android/app/prediction/AppPredictionManager.java
@@ -17,6 +17,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.content.Context;
 
 import com.android.internal.util.Preconditions;
@@ -26,6 +27,7 @@
  * @hide
  */
 @SystemApi
+@TestApi
 public final class AppPredictionManager {
 
     private final Context mContext;
diff --git a/core/java/android/app/prediction/AppPredictionSessionId.java b/core/java/android/app/prediction/AppPredictionSessionId.java
index 1d7308e..1e76c24 100644
--- a/core/java/android/app/prediction/AppPredictionSessionId.java
+++ b/core/java/android/app/prediction/AppPredictionSessionId.java
@@ -17,6 +17,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -26,6 +27,7 @@
  * @hide
  */
 @SystemApi
+@TestApi
 public final class AppPredictionSessionId implements Parcelable {
 
     private final String mId;
diff --git a/core/java/android/app/prediction/AppPredictor.java b/core/java/android/app/prediction/AppPredictor.java
index 2ddbd08..12d6ce3 100644
--- a/core/java/android/app/prediction/AppPredictor.java
+++ b/core/java/android/app/prediction/AppPredictor.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.app.prediction.IPredictionCallback.Stub;
 import android.content.Context;
 import android.content.pm.ParceledListSlice;
@@ -63,6 +64,7 @@
  * @hide
  */
 @SystemApi
+@TestApi
 public final class AppPredictor {
 
     private static final String TAG = AppPredictor.class.getSimpleName();
@@ -102,6 +104,10 @@
      * Notifies the prediction service of an app target event.
      */
     public void notifyAppTargetEvent(@NonNull AppTargetEvent event) {
+        if (mIsClosed.get()) {
+            throw new IllegalStateException("This client has already been destroyed.");
+        }
+
         try {
             mPredictionManager.notifyAppTargetEvent(mSessionId, event);
         } catch (RemoteException e) {
@@ -114,6 +120,10 @@
      */
     public void notifyLocationShown(@NonNull String launchLocation,
             @NonNull List<AppTargetId> targetIds) {
+        if (mIsClosed.get()) {
+            throw new IllegalStateException("This client has already been destroyed.");
+        }
+
         try {
             mPredictionManager.notifyLocationShown(mSessionId, launchLocation,
                     new ParceledListSlice<>(targetIds));
@@ -130,6 +140,10 @@
      */
     public void registerPredictionUpdates(@NonNull @CallbackExecutor Executor callbackExecutor,
             @NonNull AppPredictor.Callback callback) {
+        if (mIsClosed.get()) {
+            throw new IllegalStateException("This client has already been destroyed.");
+        }
+
         if (mRegisteredCallbacks.containsKey(callback)) {
             // Skip if this callback is already registered
             return;
@@ -149,6 +163,10 @@
      * callback until the callback is re-registered.
      */
     public void unregisterPredictionUpdates(@NonNull AppPredictor.Callback callback) {
+        if (mIsClosed.get()) {
+            throw new IllegalStateException("This client has already been destroyed.");
+        }
+
         if (!mRegisteredCallbacks.containsKey(callback)) {
             // Skip if this callback was never registered
             return;
@@ -168,6 +186,10 @@
      * @see Callback#onTargetsAvailable(List)
      */
     public void requestPredictionUpdate() {
+        if (mIsClosed.get()) {
+            throw new IllegalStateException("This client has already been destroyed.");
+        }
+
         try {
             mPredictionManager.requestPredictionUpdate(mSessionId);
         } catch (RemoteException e) {
@@ -182,6 +204,10 @@
     @Nullable
     public void sortTargets(@NonNull List<AppTarget> targets,
             @NonNull Executor callbackExecutor, @NonNull Consumer<List<AppTarget>> callback) {
+        if (mIsClosed.get()) {
+            throw new IllegalStateException("This client has already been destroyed.");
+        }
+
         try {
             mPredictionManager.sortAppTargets(mSessionId, new ParceledListSlice(targets),
                     new CallbackWrapper(callbackExecutor, callback));
@@ -206,6 +232,8 @@
             } catch (RemoteException e) {
                 Log.e(TAG, "Failed to notify app target event", e);
             }
+        } else {
+            throw new IllegalStateException("This client has already been destroyed.");
         }
     }
 
@@ -222,6 +250,16 @@
     }
 
     /**
+     * TODO(b/123591863): Add java docs
+     *
+     * @hide
+     */
+    @TestApi
+    public AppPredictionSessionId getSessionId() {
+        return mSessionId;
+    }
+
+    /**
      * Callback for receiving prediction updates.
      */
     public interface Callback {
diff --git a/core/java/android/app/prediction/AppTarget.java b/core/java/android/app/prediction/AppTarget.java
index 99c1c44..b924cec 100644
--- a/core/java/android/app/prediction/AppTarget.java
+++ b/core/java/android/app/prediction/AppTarget.java
@@ -18,6 +18,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.content.pm.ShortcutInfo;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -30,6 +31,7 @@
  * @hide
  */
 @SystemApi
+@TestApi
 public final class AppTarget implements Parcelable {
 
     private final AppTargetId mId;
@@ -42,8 +44,12 @@
     private int mRank;
 
     /**
+     * TODO(b/123591863): Add java docs
+     *
      * @hide
      */
+    @SystemApi
+    @TestApi
     public AppTarget(@NonNull AppTargetId id, @NonNull String packageName,
             @Nullable String className, @NonNull UserHandle user) {
         mId = id;
@@ -55,15 +61,19 @@
     }
 
     /**
+     * TODO(b/123591863): Add java docs
+     *
      * @hide
      */
-    public AppTarget(@NonNull AppTargetId id, @NonNull ShortcutInfo shortcutInfo) {
+    @SystemApi
+    public AppTarget(@NonNull AppTargetId id, @NonNull ShortcutInfo shortcutInfo,
+            @Nullable String className) {
         mId = id;
         mShortcutInfo = Preconditions.checkNotNull(shortcutInfo);
 
         mPackageName = mShortcutInfo.getPackage();
         mUser = mShortcutInfo.getUserHandle();
-        mClassName = null;
+        mClassName = className;
     }
 
     private AppTarget(Parcel parcel) {
@@ -71,13 +81,12 @@
         mShortcutInfo = parcel.readTypedObject(ShortcutInfo.CREATOR);
         if (mShortcutInfo == null) {
             mPackageName = parcel.readString();
-            mClassName = parcel.readString();
             mUser = UserHandle.of(parcel.readInt());
         } else {
             mPackageName = mShortcutInfo.getPackage();
             mUser = mShortcutInfo.getUserHandle();
-            mClassName = null;
         }
+        mClassName = parcel.readString();
         mRank = parcel.readInt();
     }
 
@@ -129,11 +138,32 @@
         mRank = rank;
     }
 
+    /**
+     * Returns the rank for the target.
+     */
     public int getRank() {
         return mRank;
     }
 
     @Override
+    public boolean equals(Object o) {
+        if (!getClass().equals(o != null ? o.getClass() : null)) return false;
+
+        AppTarget other = (AppTarget) o;
+        boolean sameClassName = (mClassName == null && other.mClassName == null)
+                || (mClassName != null && mClassName.equals(other.mClassName));
+        boolean sameShortcutInfo = (mShortcutInfo == null && other.mShortcutInfo == null)
+                || (mShortcutInfo != null && other.mShortcutInfo != null
+                        && mShortcutInfo.getId() == other.mShortcutInfo.getId());
+        return mId.equals(other.mId)
+                && mPackageName.equals(other.mPackageName)
+                && sameClassName
+                && mUser.equals(other.mUser)
+                && sameShortcutInfo
+                && mRank == other.mRank;
+    }
+
+    @Override
     public int describeContents() {
         return 0;
     }
@@ -144,9 +174,9 @@
         dest.writeTypedObject(mShortcutInfo, flags);
         if (mShortcutInfo == null) {
             dest.writeString(mPackageName);
-            dest.writeString(mClassName);
             dest.writeInt(mUser.getIdentifier());
         }
+        dest.writeString(mClassName);
         dest.writeInt(mRank);
     }
 
diff --git a/core/java/android/app/prediction/AppTargetEvent.java b/core/java/android/app/prediction/AppTargetEvent.java
index 18317e1..37e41de 100644
--- a/core/java/android/app/prediction/AppTargetEvent.java
+++ b/core/java/android/app/prediction/AppTargetEvent.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -30,6 +31,7 @@
  * @hide
  */
 @SystemApi
+@TestApi
 public final class AppTargetEvent implements Parcelable {
 
     /**
@@ -96,6 +98,16 @@
     }
 
     @Override
+    public boolean equals(Object o) {
+        if (!getClass().equals(o != null ? o.getClass() : null)) return false;
+
+        AppTargetEvent other = (AppTargetEvent) o;
+        return mTarget.equals(other.mTarget)
+                && mLocation.equals(other.mLocation)
+                && mAction == other.mAction;
+    }
+
+    @Override
     public int describeContents() {
         return 0;
     }
@@ -126,6 +138,7 @@
      * @hide
      */
     @SystemApi
+    @TestApi
     public static final class Builder {
         private AppTarget mTarget;
         private String mLocation;
diff --git a/core/java/android/app/prediction/AppTargetId.java b/core/java/android/app/prediction/AppTargetId.java
index 0b8fb47..639ba78 100644
--- a/core/java/android/app/prediction/AppTargetId.java
+++ b/core/java/android/app/prediction/AppTargetId.java
@@ -17,6 +17,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -25,14 +26,19 @@
  * @hide
  */
 @SystemApi
+@TestApi
 public final class AppTargetId implements Parcelable {
 
     @NonNull
     private final String mId;
 
     /**
+     * TODO(b/123591863): Add java docs
+     *
      * @hide
      */
+    @SystemApi
+    @TestApi
     public AppTargetId(@NonNull String id) {
         mId = id;
     }
diff --git a/core/java/android/service/appprediction/AppPredictionService.java b/core/java/android/service/appprediction/AppPredictionService.java
index b77405a..d012851 100644
--- a/core/java/android/service/appprediction/AppPredictionService.java
+++ b/core/java/android/service/appprediction/AppPredictionService.java
@@ -21,6 +21,7 @@
 import android.annotation.MainThread;
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.app.Service;
 import android.app.prediction.AppPredictionContext;
 import android.app.prediction.AppPredictionSessionId;
@@ -49,6 +50,7 @@
  * @hide
  */
 @SystemApi
+@TestApi
 public abstract class AppPredictionService extends Service {
 
     private static final String TAG = "AppPredictionService";
@@ -140,6 +142,7 @@
 
     @Override
     public final IBinder onBind(Intent intent) {
+        // TODO(b/111701043): Verify that the action is valid
         return mInterface.asBinder();
     }
 
@@ -228,6 +231,7 @@
     public void onStopPredictionUpdates() {}
 
     private void doRequestPredictionUpdate(@NonNull AppPredictionSessionId sessionId) {
+        // Just an optimization, if there are no callbacks, then don't bother notifying the service
         final ArrayList<CallbackWrapper> callbacks = mSessionCallbacks.get(sessionId);
         if (callbacks != null && !callbacks.isEmpty()) {
             onRequestPredictionUpdate(sessionId);