/* * 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 android.annotation.MainThread; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.app.ActivityManager; import android.app.ActivityOptions; import android.app.PendingIntent; import android.car.Car; import android.car.builtin.util.Slogf; import android.car.builtin.view.ViewHelper; import android.content.Context; import android.content.Intent; import android.graphics.Rect; import android.graphics.Region; import android.os.Binder; import android.os.UserManager; import android.util.Slog; import android.view.Display; import android.view.SurfaceControl; import java.util.concurrent.Executor; /** * A {@link ControlledRemoteCarTaskView} should be used when the launch intent of the task is known * before hand. * * The underlying task will be restarted if it is crashed depending on the * {@link ControlledRemoteCarTaskViewConfig#shouldAutoRestartOnCrash()}. * *

It should be preferred when: *

* * @hide */ @SystemApi public final class ControlledRemoteCarTaskView extends RemoteCarTaskView { private static final String TAG = ControlledRemoteCarTaskView.class.getSimpleName(); private final Executor mCallbackExecutor; private final ControlledRemoteCarTaskViewCallback mCallback; private final UserManager mUserManager; private final CarTaskViewController mCarTaskViewController; private final Context mContext; private final ControlledRemoteCarTaskViewConfig mConfig; private final Rect mTmpRect = new Rect(); private ActivityManager.RunningTaskInfo mTaskInfo; @Nullable private RunnerWithBackoff mStartActivityWithBackoff; final ICarTaskViewClient mICarTaskViewClient = new ICarTaskViewClient.Stub() { @Override public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) { long identity = Binder.clearCallingIdentity(); try { mTaskInfo = taskInfo; updateWindowBounds(); if (taskInfo.taskDescription != null) { ViewHelper.seResizeBackgroundColor(ControlledRemoteCarTaskView.this, taskInfo.taskDescription.getBackgroundColor()); } ControlledRemoteCarTaskView.this.onTaskAppeared(taskInfo, leash); } finally { Binder.restoreCallingIdentity(identity); } } @Override public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) { long identity = Binder.clearCallingIdentity(); try { if (taskInfo.taskDescription != null) { ViewHelper.seResizeBackgroundColor(ControlledRemoteCarTaskView.this, taskInfo.taskDescription.getBackgroundColor()); } ControlledRemoteCarTaskView.this.onTaskInfoChanged(taskInfo); } finally { Binder.restoreCallingIdentity(identity); } } @Override public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) { long identity = Binder.clearCallingIdentity(); try { mTaskInfo = null; ControlledRemoteCarTaskView.this.onTaskVanished(taskInfo); } finally { Binder.restoreCallingIdentity(identity); } } @Override public void setResizeBackgroundColor(SurfaceControl.Transaction t, int color) { ViewHelper.seResizeBackgroundColor(ControlledRemoteCarTaskView.this, color); } @Override public Rect getCurrentBoundsOnScreen() { ViewHelper.getBoundsOnScreen(ControlledRemoteCarTaskView.this, mTmpRect); return mTmpRect; } }; ControlledRemoteCarTaskView( @NonNull Context context, ControlledRemoteCarTaskViewConfig config, @NonNull Executor callbackExecutor, @NonNull ControlledRemoteCarTaskViewCallback callback, CarTaskViewController carTaskViewController, @NonNull UserManager userManager) { super(context); mContext = context; mConfig = config; mCallbackExecutor = callbackExecutor; mCallback = callback; mCarTaskViewController = carTaskViewController; mUserManager = userManager; mCallbackExecutor.execute(() -> mCallback.onTaskViewCreated(this)); if (mConfig.mShouldAutoRestartOnTaskRemoval) { mStartActivityWithBackoff = new RunnerWithBackoff(this::startActivityInternal); } } /** * Starts the underlying activity, specified as part of * {@link CarTaskViewController#createControlledRemoteCarTaskView(Executor, Intent, boolean, * ControlledRemoteCarTaskViewCallback)}. */ @RequiresPermission(Car.PERMISSION_REGISTER_CAR_SYSTEM_UI_PROXY) @MainThread public void startActivity() { if (mStartActivityWithBackoff == null) { startActivityInternal(); return; } mStartActivityWithBackoff.stop(); mStartActivityWithBackoff.start(); } private void stopTheStartActivityBackoffIfExists() { if (mStartActivityWithBackoff == null) { if (CarTaskViewController.DBG) { Slog.d(TAG, "mStartActivityWithBackoff is not present."); } return; } mStartActivityWithBackoff.stop(); } private void startActivityInternal() { if (!mUserManager.isUserUnlocked()) { if (CarTaskViewController.DBG) { Slogf.d(TAG, "Can't start activity due to user is isn't unlocked"); } return; } // Use mContext.getDisplay() instead of View#getDisplay() since // ControlledRemoteCarTaskViews can be created using window context. Using // View#getDisplay() would return null. Display display = mContext.getDisplay(); // Don't start activity when the display is off. This can happen when the taskview is not // attached to a window. if (display == null) { Slogf.w(TAG, "Can't start activity because display is not available in " + "taskview yet."); return; } // Don't start activity when the display is off for ActivityVisibilityTests. if (display.getState() != Display.STATE_ON) { Slogf.w(TAG, "Can't start activity due to the display is off"); return; } ActivityOptions options = ActivityOptions .makeCustomAnimation(mContext, /* enterResId= */ 0, /* exitResId= */ 0) .setPendingIntentBackgroundActivityStartMode( ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED); Rect launchBounds = new Rect(); ViewHelper.getBoundsOnScreen(this, launchBounds); launchBounds.set(launchBounds); if (CarTaskViewController.DBG) { Slogf.d(TAG, "Starting (" + mConfig.mActivityIntent.getComponent() + ") on " + launchBounds); } Intent fillInIntent = null; if ((mConfig.mActivityIntent.getFlags() & Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) != 0) { fillInIntent = new Intent().addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); } startActivity( PendingIntent.getActivity(mContext, /* requestCode= */ 0, mConfig.mActivityIntent, PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT), fillInIntent, options, launchBounds); } @Override void onInitialized() { mContext.getMainExecutor().execute(() -> { // Check for isReleased() because the car task view might have already been // released but this code path is executed later because the executor was busy. if (isReleased()) { Slogf.w(TAG, "car task view has already been released"); return; } startActivity(); }); mCallbackExecutor.execute(() -> mCallback.onTaskViewInitialized()); } @Override void onReleased() { mTaskInfo = null; mCallbackExecutor.execute(() -> mCallback.onTaskViewReleased()); mCarTaskViewController.onRemoteCarTaskViewReleased(this); } @Override void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) { super.onTaskAppeared(taskInfo, leash); // Stop the start activity backoff because a task has already appeared. stopTheStartActivityBackoffIfExists(); mCallbackExecutor.execute(() -> { if (isReleased()) { Slogf.w(TAG, "car task view has already been released"); return; } mCallback.onTaskAppeared(taskInfo); }); } @Override void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) { super.onTaskInfoChanged(taskInfo); mCallbackExecutor.execute(() -> { if (isReleased()) { Slogf.w(TAG, "car task view has already been released"); return; } mCallback.onTaskInfoChanged(taskInfo); }); } @RequiresPermission(Car.PERMISSION_REGISTER_CAR_SYSTEM_UI_PROXY) @Override @MainThread public void showEmbeddedTask() { super.showEmbeddedTask(); if (getTaskInfo() == null) { if (CarTaskViewController.DBG) { Slogf.d(TAG, "Embedded task not available, starting it now."); } startActivity(); } } @Override void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) { super.onTaskVanished(taskInfo); if (mConfig.mShouldAutoRestartOnTaskRemoval && mCarTaskViewController.isHostVisible()) { // onTaskVanished can be called when the host is in the background. In this case // embedded activity should not be started. Slogf.i(TAG, "Restarting task " + taskInfo.baseActivity + " in ControlledRemoteCarTaskView"); startActivity(); } mCallbackExecutor.execute(() -> { if (isReleased()) { Slogf.w(TAG, "car task view has already been released"); return; } mCallback.onTaskVanished(taskInfo); }); } /** * @return the {@link android.app.ActivityManager.RunningTaskInfo} of the task currently * running in the TaskView. */ @MainThread @Override @Nullable public ActivityManager.RunningTaskInfo getTaskInfo() { return mTaskInfo; } ControlledRemoteCarTaskViewConfig getConfig() { return mConfig; } @Override public String toString() { return toString(/* withBounds= */ false); } String toString(boolean withBounds) { if (withBounds) { ViewHelper.getBoundsOnScreen(this, mTmpRect); } return TAG + " {\n" + " config=" + mConfig + "\n" + " taskId=" + (getTaskInfo() == null ? "null" : getTaskInfo().taskId) + "\n" + (withBounds ? (" boundsOnScreen=" + mTmpRect) : "") + "}\n"; } // Since SurfaceView is public, these methods need to be overridden. Details in b/296680464. @Override @RequiresPermission(Car.PERMISSION_REGISTER_CAR_SYSTEM_UI_PROXY) @MainThread public void addInsets(int index, int type, @NonNull Rect frame) { super.addInsets(index, type, frame); } @Override @RequiresPermission(Car.PERMISSION_REGISTER_CAR_SYSTEM_UI_PROXY) public void removeInsets(int index, int type) { super.removeInsets(index, type); } @Override @MainThread public void release() { super.release(); stopTheStartActivityBackoffIfExists(); } @Override public void onAttachedToWindow() { super.onAttachedToWindow(); } @Override public void onDetachedFromWindow() { super.onDetachedFromWindow(); } @Override @MainThread public boolean isInitialized() { return super.isInitialized(); } @Override @MainThread public void setObscuredTouchRegion(@NonNull Region obscuredRegion) { super.setObscuredTouchRegion(obscuredRegion); } @Override @MainThread public void setObscuredTouchRect(@NonNull Rect obscuredRect) { super.setObscuredTouchRect(obscuredRect); } @Override @RequiresPermission(Car.PERMISSION_REGISTER_CAR_SYSTEM_UI_PROXY) @MainThread public void updateWindowBounds() { super.updateWindowBounds(); } }