CarAppViewModel.java

/*
 * 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.app.Application;
import android.content.ComponentName;
import android.content.Intent;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.annotation.VisibleForTesting;
import androidx.car.app.activity.renderer.ICarAppActivity;
import androidx.car.app.activity.renderer.IRendererCallback;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.MutableLiveData;

/**
 * The view model to keep track of the CarAppActivity data.
 *
 * This main role of this class is to extent the life of a service connection beyond the regular
 * lifecycle of an activity. This is done by making sure the unbind happens when the view model
 * clears instead of when the activity calls onDestroy.
 *
 * @hide
 */
@RestrictTo(LIBRARY)
public class CarAppViewModel extends AndroidViewModel implements ErrorHandler {
    /** Holds the information about an error event. */
    public static class ErrorEvent {
        private final ErrorType mErrorType;
        private final Throwable mException;

        public ErrorEvent(@NonNull ErrorType errorType, @NonNull Throwable exception) {
            mErrorType = errorType;
            mException = exception;
        }

        /** Returns the type of error. */
        @NonNull ErrorType getErrorType() {
            return mErrorType;
        }

        /** Returns the exception associated with this error event. */
        @NonNull Throwable getException() {
            return mException;
        }
    }

    private final MutableLiveData<ErrorEvent> mErrorEvent = new MutableLiveData<>();
    private ServiceConnectionManager mServiceConnectionManager;
    @Nullable private IRendererCallback mIRendererCallback;

    public CarAppViewModel(@NonNull Application application, @NonNull ComponentName componentName) {
        super(application);

        mServiceConnectionManager = new ServiceConnectionManager(application, componentName, this);
    }

    @VisibleForTesting
    @NonNull ServiceConnectionManager getServiceConnectionManager() {
        return mServiceConnectionManager;
    }

    @VisibleForTesting
    void setServiceConnectionManager(ServiceConnectionManager serviceConnectionManager) {
        mServiceConnectionManager = serviceConnectionManager;
    }

    @NonNull ServiceDispatcher getServiceDispatcher() {
        return mServiceConnectionManager.getServiceDispatcher();
    }

    /** Updates the rendeer callback. */
    void setRendererCallback(@NonNull IRendererCallback rendererCallback) {
        mIRendererCallback = rendererCallback;
    }

    /**
     * Binds to the renderer service and initializes the service if not bound already.
     *
     * Initializes the renderer service with given properties if already bound to the renderer
     * service.
     */
    void bind(@NonNull Intent intent, @NonNull ICarAppActivity iCarAppActivity,
            int displayId) {
        mServiceConnectionManager.bind(intent, iCarAppActivity, displayId);
    }

    /** Closes the connection to the renderer service if any. */
    void unbind() {
        mServiceConnectionManager.unbind();
    }

    @NonNull
    MutableLiveData<ErrorEvent> getErrorEvent() {
        return mErrorEvent;
    }

    @Override
    protected void onCleared() {
        super.onCleared();
        if (mIRendererCallback != null) {
            mServiceConnectionManager.getServiceDispatcher()
                    .dispatch(mIRendererCallback::onDestroyed);
        }
        mServiceConnectionManager.unbind();
    }

    @Override
    public void onError(@NonNull ErrorHandler.ErrorType errorType, @NonNull Throwable exception) {
        mErrorEvent.setValue(new ErrorEvent(errorType, exception));
    }
}