| /* |
| * Copyright 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 androidx.car.app.activity; |
| |
| import static androidx.annotation.RestrictTo.Scope.LIBRARY; |
| |
| import android.os.DeadObjectException; |
| import android.os.RemoteException; |
| import android.util.Log; |
| |
| import androidx.annotation.NonNull; |
| import androidx.annotation.Nullable; |
| import androidx.annotation.RestrictTo; |
| import androidx.annotation.VisibleForTesting; |
| import androidx.car.app.activity.renderer.IRendererService; |
| import androidx.car.app.serialization.BundlerException; |
| |
| /** |
| * {@link IRendererService} messages dispatcher, responsible for IPC error handling. |
| * |
| * @hide |
| */ |
| @RestrictTo(LIBRARY) |
| public class ServiceDispatcher { |
| |
| /** An interface for monitoring the binding state of a service connection. */ |
| public interface OnBindingListener { |
| /** Returns true if the service connection is bound. */ |
| boolean isBound(); |
| } |
| |
| private final ErrorHandler mErrorHandler; |
| private OnBindingListener mOnBindingListener; |
| |
| /** A one way call to the service */ |
| public interface OneWayCall { |
| /** Remote invocation to execute */ |
| void invoke() throws RemoteException, BundlerException; |
| } |
| |
| /** |
| * A call to fetch a value from the service. This call will block the thread until the value |
| * is received |
| * |
| * @param <T> Type of value to be returned |
| */ |
| // TODO(b/184697399): Remove blocking return callbacks. |
| public interface ReturnCall<T> { |
| /** Remote invocation to execute */ |
| @Nullable |
| T invoke() throws RemoteException, BundlerException; |
| } |
| |
| public ServiceDispatcher(@NonNull ErrorHandler errorHandler, |
| @NonNull OnBindingListener onBindingListener) { |
| mErrorHandler = errorHandler; |
| mOnBindingListener = onBindingListener; |
| } |
| |
| @VisibleForTesting |
| public void setOnBindingListener(@NonNull OnBindingListener onBindingListener) { |
| mOnBindingListener = onBindingListener; |
| } |
| |
| /** Dispatches the given {@link OneWayCall}. This is a non-blocking call. */ |
| public void dispatch(@NonNull String description, @NonNull OneWayCall call) { |
| fetch(description, null, (ReturnCall<Void>) () -> { |
| call.invoke(); |
| return null; |
| }); |
| } |
| |
| /** Dispatches the given {@link OneWayCall}. Ignores any errors. This is a non-blocking call. */ |
| public void dispatchNoFail(@NonNull String description, @NonNull OneWayCall call) { |
| fetchNoFail(description, null, (ReturnCall<Void>) () -> { |
| call.invoke(); |
| return null; |
| }); |
| } |
| |
| /** |
| * Retrieves a value from the service handling any communication error and displaying the |
| * error to the user. |
| * |
| * <p>This is a blocking call |
| * |
| * @param description name for logging purposes |
| * @param fallbackValue value to return in case the call is unsuccessful |
| * @param call code to execute to retrieve the value |
| * @return the value retrieved or the {@code fallbackValue} if the call failed |
| */ |
| // TODO(b/184697399): Remove two-way calls as these are blocking. |
| @Nullable |
| public <T> T fetch(@NonNull String description, @Nullable T fallbackValue, |
| @NonNull ReturnCall<T> call) { |
| if (!mOnBindingListener.isBound()) { |
| // Avoid dispatching messages if we are not bound to the service |
| return fallbackValue; |
| } |
| try { |
| // TODO(b/184697267): Implement ANR (application not responding) checks |
| return call.invoke(); |
| } catch (DeadObjectException e) { |
| Log.e(LogTags.TAG, "Connection lost", e); |
| mErrorHandler.onError(ErrorHandler.ErrorType.HOST_CONNECTION_LOST); |
| } catch (RemoteException e) { |
| Log.e(LogTags.TAG, "Remote exception (host render service)", e); |
| mErrorHandler.onError(ErrorHandler.ErrorType.HOST_ERROR); |
| } catch (BundlerException e) { |
| Log.e(LogTags.TAG, "Bundler exception (protocol)", e); |
| mErrorHandler.onError(ErrorHandler.ErrorType.CLIENT_SIDE_ERROR); |
| } catch (RuntimeException e) { |
| Log.e(LogTags.TAG, "Runtime exception (unknown)", e); |
| mErrorHandler.onError(ErrorHandler.ErrorType.UNKNOWN_ERROR); |
| } |
| return fallbackValue; |
| } |
| |
| /** |
| * Retrieves a value from the service, ignoring any communication error and just returning |
| * the {@code fallbackValue} if an error is encountered in the communication. |
| * |
| * <p>This is a blocking call |
| * |
| * @param description name for logging purposes |
| * @param fallbackValue value to return in case the call is unsuccessful |
| * @param call code to execute to retrieve the value |
| * @return the value retrieved or the {@code fallbackValue} if the call failed |
| */ |
| @Nullable |
| public <T> T fetchNoFail(@NonNull String description, @Nullable T fallbackValue, |
| @NonNull ReturnCall<T> call) { |
| if (!mOnBindingListener.isBound()) { |
| // Avoid dispatching messages if we are not bound to the service |
| return fallbackValue; |
| } |
| try { |
| // TODO(b/184697267): Implement ANR (application not responding) checks |
| return call.invoke(); |
| } catch (RemoteException e) { |
| Log.e(LogTags.TAG, "Remote exception (host render service)", e); |
| } catch (BundlerException e) { |
| Log.e(LogTags.TAG, "Bundler exception (protocol)", e); |
| } catch (RuntimeException e) { |
| Log.e(LogTags.TAG, "Runtime exception (unknown)", e); |
| } |
| return fallbackValue; |
| } |
| } |