/* * Copyright (C) 2023 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 com.android.car.internal; import android.annotation.Nullable; import android.car.IResultCallback; import android.car.ResultCallback; import android.car.builtin.util.Slogf; import android.os.Parcel; import android.os.Parcelable; import android.util.Log; import com.android.internal.annotations.GuardedBy; import java.util.Objects; import java.util.concurrent.Executor; /** * Implements a Parcelable class which takes {@link ResultCallback} as input and provides * {@link #complete(Object)} call to the binder receiver to send result back asynchronously. * *

* Managers can create this class with {@link ResultCallback} and {@link Executor} and pass it * across the binder from manager to service. On the service side, when results are available, * {@link #complete(Object)} should be called with the result. * * @param refer to a Parcelable object. * * @hide */ public class ResultCallbackImpl implements Parcelable { private static final String TAG = ResultCallbackImpl.class.getSimpleName(); private static final boolean DBG = Slogf.isLoggable(TAG, Log.DEBUG); private final Object mLock = new Object(); // TODO(b/269040634): Revisit this logic based on the CL discussion @GuardedBy("mLock") @Nullable private IResultCallback mReceiver; @Nullable private final ResultCallback mCallback; @Nullable private final Executor mExecutor; public ResultCallbackImpl(Executor executor, ResultCallback callback) { mExecutor = Objects.requireNonNull(executor, "executor cannot be null"); mCallback = Objects.requireNonNull(callback, "callback cannot be null"); } ResultCallbackImpl(Parcel in) { mExecutor = null; mCallback = null; mReceiver = IResultCallback.Stub.asInterface(in.readStrongBinder()); } /** * Calls to update the {code result}. */ public void complete(V result) { try { IResultCallback receiver; synchronized (mLock) { receiver = mReceiver; } if (receiver == null) { // Receive could be null if the call is not IPC. sendResult(result); return; } ResultWrapper wrapper = new ResultWrapper(result); receiver.complete(wrapper); } catch (Exception e) { Slogf.w(TAG, e, "ResultCallbackImpl failed."); } } /** * Called before sending the sending the result to the callback. */ protected void onCompleted(V result) {} private void sendResult(V result) { if (mExecutor == null || mCallback == null) { // This should never happen. sendResult call will always be executed on the sender side. Slogf.wtf(TAG, "Executor or callback can not be null. Executor:%s Callback:%s", mExecutor, mCallback); return; } if (DBG) { Slogf.d(TAG, "Sending results - %s", result.toString()); } onCompleted(result); mExecutor.execute(() -> mCallback.onResult(result)); } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { synchronized (mLock) { if (mReceiver == null) { mReceiver = new MyCallbackReceiver(); } dest.writeStrongBinder(mReceiver.asBinder()); } } private final class MyCallbackReceiver extends IResultCallback.Stub { @Override public void complete(ResultWrapper resultCallback) { sendResult((V) resultCallback.getResult()); } } public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { public ResultCallbackImpl createFromParcel(Parcel in) { return new ResultCallbackImpl(in); } public ResultCallbackImpl[] newArray(int size) { return new ResultCallbackImpl[size]; } }; }