/* * 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 android.car.app; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import android.Manifest; import android.annotation.MainThread; import android.annotation.NonNull; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.UiContext; import android.app.Activity; import android.car.Car; import android.car.builtin.util.Slogf; import android.content.Context; import android.os.RemoteException; import android.os.UserManager; import android.util.Log; import android.util.Slog; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.concurrent.Executor; /** * This class is used for creating task views & is created on a per activity basis. * @hide */ @SystemApi public final class CarTaskViewController { private static final String TAG = CarTaskViewController.class.getSimpleName(); static final boolean DBG = Slogf.isLoggable(TAG, Log.DEBUG); private final ICarSystemUIProxy mService; private final Context mHostContext; private final CarTaskViewControllerHostLifecycle mLifecycle; private final List mRemoteCarTaskViews = new ArrayList<>(); private final CarTaskViewInputInterceptor mTaskViewInputInterceptor; private final ICarActivityService mCarActivityService; private boolean mReleased = false; /** * @param service the binder interface to communicate with the car system UI. * @hide */ CarTaskViewController(@UiContext Context hostContext, @NonNull CarTaskViewControllerHostLifecycle lifecycle, @NonNull ICarSystemUIProxy service, ICarActivityService carActivityService) { mHostContext = hostContext; mService = service; mLifecycle = lifecycle; mCarActivityService = carActivityService; mTaskViewInputInterceptor = new CarTaskViewInputInterceptor(hostContext, lifecycle, this); } /** * Creates a new {@link ControlledRemoteCarTaskView}. * * @param callbackExecutor the executor to get the {@link ControlledRemoteCarTaskViewCallback} * on. * @param controlledRemoteCarTaskViewCallback the callback to monitor the * {@link ControlledRemoteCarTaskView} related * events. */ @RequiresPermission(allOf = {Manifest.permission.INJECT_EVENTS, Manifest.permission.INTERNAL_SYSTEM_WINDOW}, conditional = true) @MainThread public void createControlledRemoteCarTaskView( @NonNull ControlledRemoteCarTaskViewConfig controlledRemoteCarTaskViewConfig, @NonNull Executor callbackExecutor, @NonNull ControlledRemoteCarTaskViewCallback controlledRemoteCarTaskViewCallback) { if (mReleased) { throw new IllegalStateException("CarTaskViewController is already released"); } ControlledRemoteCarTaskView taskViewClient = new ControlledRemoteCarTaskView( mHostContext, controlledRemoteCarTaskViewConfig, callbackExecutor, controlledRemoteCarTaskViewCallback, /* carTaskViewController= */ this, mHostContext.getSystemService(UserManager.class)); try { ICarTaskViewHost host = mService.createControlledCarTaskView( taskViewClient.mICarTaskViewClient); taskViewClient.setRemoteHost(host); mRemoteCarTaskViews.add(taskViewClient); if (controlledRemoteCarTaskViewConfig.mShouldCaptureGestures || controlledRemoteCarTaskViewConfig.mShouldCaptureLongPress) { assertPermission(Manifest.permission.INJECT_EVENTS); assertPermission(Manifest.permission.INTERNAL_SYSTEM_WINDOW); mTaskViewInputInterceptor.init(); } } catch (RemoteException e) { Slogf.e(TAG, "Unable to create task view.", e); } } /** * Creates a new {@link RemoteCarRootTaskView}. * * @param callbackExecutor the executor to get the {@link RemoteCarRootTaskViewCallback} on. * @param remoteCarRootTaskViewCallback the callback to monitor the * {@link RemoteCarRootTaskView} related events. * @hide */ @RequiresPermission(Car.PERMISSION_CONTROL_CAR_APP_LAUNCH) @MainThread public void createRemoteCarRootTaskView( @NonNull RemoteCarRootTaskViewConfig remoteCarRootTaskViewConfig, @NonNull Executor callbackExecutor, @NonNull RemoteCarRootTaskViewCallback remoteCarRootTaskViewCallback) { assertPermission(Car.PERMISSION_CONTROL_CAR_APP_LAUNCH); if (mReleased) { throw new IllegalStateException("CarTaskViewController is already released"); } RemoteCarRootTaskView taskViewClient = new RemoteCarRootTaskView( mHostContext, remoteCarRootTaskViewConfig, callbackExecutor, remoteCarRootTaskViewCallback, /* carTaskViewController= */ this, mCarActivityService ); try { ICarTaskViewHost host = mService.createCarTaskView(taskViewClient.mICarTaskViewClient); taskViewClient.setRemoteHost(host); mRemoteCarTaskViews.add(taskViewClient); } catch (RemoteException e) { Slogf.e(TAG, "Unable to create root task view.", e); } } /** * Creates a new {@link RemoteCarDefaultRootTaskView}. * * @param callbackExecutor the executor to get the {@link RemoteCarDefaultRootTaskViewCallback} * on. * @param remoteCarDefaultRootTaskViewCallback the callback to monitor the * {@link RemoteCarDefaultRootTaskView} related * events. * @hide */ @MainThread public void createRemoteCarDefaultRootTaskView( @NonNull RemoteCarDefaultRootTaskViewConfig remoteCarDefaultRootTaskViewConfig, @NonNull Executor callbackExecutor, @NonNull RemoteCarDefaultRootTaskViewCallback remoteCarDefaultRootTaskViewCallback) { if (mReleased) { throw new IllegalStateException("CarTaskViewController is already released"); } RemoteCarDefaultRootTaskView taskViewClient = new RemoteCarDefaultRootTaskView( mHostContext, remoteCarDefaultRootTaskViewConfig, callbackExecutor, remoteCarDefaultRootTaskViewCallback, /* carTaskViewController= */ this ); try { ICarTaskViewHost host = mService.createCarTaskView( taskViewClient.mICarTaskViewClient); taskViewClient.setRemoteHost(host); mRemoteCarTaskViews.add(taskViewClient); } catch (RemoteException e) { Slogf.e(TAG, "Unable to create default root task view.", e); } } void onRemoteCarTaskViewReleased(@NonNull RemoteCarTaskView taskView) { if (mReleased) { Slog.w(TAG, "Failed to remove the taskView as the " + "CarTaskViewController is already released"); return; } if (!mRemoteCarTaskViews.contains(taskView)) { Slog.w(TAG, "This taskView has already been removed"); return; } mRemoteCarTaskViews.remove(taskView); } private void assertPermission(String permission) { if (mHostContext.checkCallingOrSelfPermission(permission) != PERMISSION_GRANTED) { throw new SecurityException("requires " + permission); } } /** * Releases all the resources held by the taskviews associated with this controller. * *

Once {@link #release()} is called, the current instance of {@link CarTaskViewController} * cannot be used further. A new instance should be requested using * {@link CarActivityManager#getCarTaskViewController(Activity, Executor, * CarTaskViewControllerCallback)}. */ @MainThread public void release() { if (mReleased) { Slogf.w(TAG, "CarTaskViewController is already released"); return; } releaseTaskViews(); mTaskViewInputInterceptor.release(); mReleased = true; } @MainThread void releaseTaskViews() { Iterator iterator = mRemoteCarTaskViews.iterator(); while (iterator.hasNext()) { RemoteCarTaskView taskView = iterator.next(); // Remove the task view here itself because release triggers removal again which can // result in concurrent modification exception. iterator.remove(); taskView.release(); } } /** * Brings all the embedded tasks to the front. */ @MainThread public void showEmbeddedTasks() { if (mReleased) { throw new IllegalStateException("CarTaskViewController is already released"); } for (int i = 0, length = mRemoteCarTaskViews.size(); i < length; i++) { RemoteCarTaskView remoteCarTaskView = mRemoteCarTaskViews.get(i); // TODO(b/267314188): Add a new method in ICarSystemUI to call // showEmbeddedTask in a single WCT for multiple tasks. remoteCarTaskView.showEmbeddedTask(); } } /** * Brings all the embedded controlled tasks to the front. */ @MainThread void showEmbeddedControlledTasks() { if (mReleased) { throw new IllegalStateException("CarTaskViewController is already released"); } for (int i = 0, length = mRemoteCarTaskViews.size(); i < length; i++) { RemoteCarTaskView carTaskView = mRemoteCarTaskViews.get(i); // TODO(b/267314188): Add a new method in ICarSystemUI to call // showEmbeddedTask in a single WCT for multiple tasks. if (carTaskView instanceof ControlledRemoteCarTaskView) { carTaskView.showEmbeddedTask(); } } } boolean isHostVisible() { return mLifecycle.isVisible(); } List getRemoteCarTaskViews() { return mRemoteCarTaskViews; } }