1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.car.app;
18 
19 
20 import android.annotation.MainThread;
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.annotation.RequiresPermission;
24 import android.annotation.SystemApi;
25 import android.app.ActivityManager;
26 import android.app.ActivityOptions;
27 import android.app.PendingIntent;
28 import android.car.Car;
29 import android.car.builtin.util.Slogf;
30 import android.car.builtin.view.ViewHelper;
31 import android.content.Context;
32 import android.content.Intent;
33 import android.graphics.Rect;
34 import android.graphics.Region;
35 import android.os.Binder;
36 import android.os.UserManager;
37 import android.util.Slog;
38 import android.view.Display;
39 import android.view.SurfaceControl;
40 
41 import java.util.concurrent.Executor;
42 
43 /**
44  * A {@link ControlledRemoteCarTaskView} should be used when the launch intent of the task is known
45  * before hand.
46  *
47  * The underlying task will be restarted if it is crashed depending on the
48  * {@link ControlledRemoteCarTaskViewConfig#shouldAutoRestartOnCrash()}.
49  *
50  * <p>It should be preferred when:
51  * <ul>
52  *     <li>The underlying task is meant to be started by the host and be there forever.</li>
53  * </ul>
54  *
55  * @hide
56  */
57 @SystemApi
58 public final class ControlledRemoteCarTaskView extends RemoteCarTaskView {
59     private static final String TAG = ControlledRemoteCarTaskView.class.getSimpleName();
60 
61     private final Executor mCallbackExecutor;
62     private final ControlledRemoteCarTaskViewCallback mCallback;
63     private final UserManager mUserManager;
64     private final CarTaskViewController mCarTaskViewController;
65     private final Context mContext;
66     private final ControlledRemoteCarTaskViewConfig mConfig;
67     private final Rect mTmpRect = new Rect();
68 
69     private ActivityManager.RunningTaskInfo mTaskInfo;
70     @Nullable private RunnerWithBackoff mStartActivityWithBackoff;
71 
72     final ICarTaskViewClient mICarTaskViewClient = new ICarTaskViewClient.Stub() {
73         @Override
74         public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
75             long identity = Binder.clearCallingIdentity();
76             try {
77                 mTaskInfo = taskInfo;
78                 updateWindowBounds();
79                 if (taskInfo.taskDescription != null) {
80                     ViewHelper.seResizeBackgroundColor(ControlledRemoteCarTaskView.this,
81                             taskInfo.taskDescription.getBackgroundColor());
82                 }
83                 ControlledRemoteCarTaskView.this.onTaskAppeared(taskInfo, leash);
84             } finally {
85                 Binder.restoreCallingIdentity(identity);
86             }
87         }
88 
89         @Override
90         public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
91             long identity = Binder.clearCallingIdentity();
92             try {
93                 if (taskInfo.taskDescription != null) {
94                     ViewHelper.seResizeBackgroundColor(ControlledRemoteCarTaskView.this,
95                             taskInfo.taskDescription.getBackgroundColor());
96                 }
97                 ControlledRemoteCarTaskView.this.onTaskInfoChanged(taskInfo);
98             } finally {
99                 Binder.restoreCallingIdentity(identity);
100             }
101         }
102 
103         @Override
104         public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
105             long identity = Binder.clearCallingIdentity();
106             try {
107                 mTaskInfo = null;
108                 ControlledRemoteCarTaskView.this.onTaskVanished(taskInfo);
109             } finally {
110                 Binder.restoreCallingIdentity(identity);
111             }
112         }
113 
114         @Override
115         public void setResizeBackgroundColor(SurfaceControl.Transaction t, int color) {
116             ViewHelper.seResizeBackgroundColor(ControlledRemoteCarTaskView.this, color);
117         }
118 
119         @Override
120         public Rect getCurrentBoundsOnScreen() {
121             ViewHelper.getBoundsOnScreen(ControlledRemoteCarTaskView.this, mTmpRect);
122             return mTmpRect;
123         }
124     };
125 
ControlledRemoteCarTaskView( @onNull Context context, ControlledRemoteCarTaskViewConfig config, @NonNull Executor callbackExecutor, @NonNull ControlledRemoteCarTaskViewCallback callback, CarTaskViewController carTaskViewController, @NonNull UserManager userManager)126     ControlledRemoteCarTaskView(
127             @NonNull Context context,
128             ControlledRemoteCarTaskViewConfig config,
129             @NonNull Executor callbackExecutor,
130             @NonNull ControlledRemoteCarTaskViewCallback callback,
131             CarTaskViewController carTaskViewController,
132             @NonNull UserManager userManager) {
133         super(context);
134         mContext = context;
135         mConfig = config;
136         mCallbackExecutor = callbackExecutor;
137         mCallback = callback;
138         mCarTaskViewController = carTaskViewController;
139         mUserManager = userManager;
140 
141         mCallbackExecutor.execute(() -> mCallback.onTaskViewCreated(this));
142         if (mConfig.mShouldAutoRestartOnTaskRemoval) {
143             mStartActivityWithBackoff = new RunnerWithBackoff(this::startActivityInternal);
144         }
145     }
146 
147     /**
148      * Starts the underlying activity, specified as part of
149      * {@link CarTaskViewController#createControlledRemoteCarTaskView(Executor, Intent, boolean,
150      * ControlledRemoteCarTaskViewCallback)}.
151      */
152     @RequiresPermission(Car.PERMISSION_REGISTER_CAR_SYSTEM_UI_PROXY)
153     @MainThread
startActivity()154     public void startActivity() {
155         if (mStartActivityWithBackoff == null) {
156             startActivityInternal();
157             return;
158         }
159         mStartActivityWithBackoff.stop();
160         mStartActivityWithBackoff.start();
161     }
162 
stopTheStartActivityBackoffIfExists()163     private void stopTheStartActivityBackoffIfExists() {
164         if (mStartActivityWithBackoff == null) {
165             if (CarTaskViewController.DBG) {
166                 Slog.d(TAG, "mStartActivityWithBackoff is not present.");
167             }
168             return;
169         }
170         mStartActivityWithBackoff.stop();
171     }
172 
startActivityInternal()173     private void startActivityInternal() {
174         if (!mUserManager.isUserUnlocked()) {
175             if (CarTaskViewController.DBG) {
176                 Slogf.d(TAG, "Can't start activity due to user is isn't unlocked");
177             }
178             return;
179         }
180 
181         // Use mContext.getDisplay() instead of View#getDisplay() since
182         // ControlledRemoteCarTaskViews can be created using window context. Using
183         // View#getDisplay() would return null.
184         Display display = mContext.getDisplay();
185 
186         // Don't start activity when the display is off. This can happen when the taskview is not
187         // attached to a window.
188         if (display == null) {
189             Slogf.w(TAG, "Can't start activity because display is not available in "
190                     + "taskview yet.");
191             return;
192         }
193         // Don't start activity when the display is off for ActivityVisibilityTests.
194         if (display.getState() != Display.STATE_ON) {
195             Slogf.w(TAG, "Can't start activity due to the display is off");
196             return;
197         }
198 
199         ActivityOptions options = ActivityOptions
200                 .makeCustomAnimation(mContext, /* enterResId= */ 0, /* exitResId= */ 0)
201                 .setPendingIntentBackgroundActivityStartMode(
202                         ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
203         Rect launchBounds = new Rect();
204         ViewHelper.getBoundsOnScreen(this, launchBounds);
205         launchBounds.set(launchBounds);
206         if (CarTaskViewController.DBG) {
207             Slogf.d(TAG, "Starting (" + mConfig.mActivityIntent.getComponent() + ") on "
208                     + launchBounds);
209         }
210         Intent fillInIntent = null;
211         if ((mConfig.mActivityIntent.getFlags() & Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) != 0) {
212             fillInIntent = new Intent().addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
213         }
214         startActivity(
215                 PendingIntent.getActivity(mContext, /* requestCode= */ 0,
216                         mConfig.mActivityIntent,
217                         PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT),
218                 fillInIntent, options, launchBounds);
219     }
220 
221     @Override
onInitialized()222     void onInitialized() {
223         mContext.getMainExecutor().execute(() -> {
224             // Check for isReleased() because the car task view might have already been
225             // released but this code path is executed later because the executor was busy.
226             if (isReleased()) {
227                 Slogf.w(TAG, "car task view has already been released");
228                 return;
229             }
230             startActivity();
231         });
232         mCallbackExecutor.execute(() -> mCallback.onTaskViewInitialized());
233     }
234 
235     @Override
onReleased()236     void onReleased() {
237         mTaskInfo = null;
238         mCallbackExecutor.execute(() -> mCallback.onTaskViewReleased());
239         mCarTaskViewController.onRemoteCarTaskViewReleased(this);
240     }
241 
242     @Override
onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash)243     void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
244         super.onTaskAppeared(taskInfo, leash);
245         // Stop the start activity backoff because a task has already appeared.
246         stopTheStartActivityBackoffIfExists();
247         mCallbackExecutor.execute(() -> {
248             if (isReleased()) {
249                 Slogf.w(TAG, "car task view has already been released");
250                 return;
251             }
252             mCallback.onTaskAppeared(taskInfo);
253         });
254     }
255 
256     @Override
onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo)257     void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
258         super.onTaskInfoChanged(taskInfo);
259         mCallbackExecutor.execute(() -> {
260             if (isReleased()) {
261                 Slogf.w(TAG, "car task view has already been released");
262                 return;
263             }
264             mCallback.onTaskInfoChanged(taskInfo);
265         });
266     }
267 
268     @RequiresPermission(Car.PERMISSION_REGISTER_CAR_SYSTEM_UI_PROXY)
269     @Override
270     @MainThread
showEmbeddedTask()271     public void showEmbeddedTask() {
272         super.showEmbeddedTask();
273         if (getTaskInfo() == null) {
274             if (CarTaskViewController.DBG) {
275                 Slogf.d(TAG, "Embedded task not available, starting it now.");
276             }
277             startActivity();
278         }
279     }
280 
281     @Override
onTaskVanished(ActivityManager.RunningTaskInfo taskInfo)282     void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
283         super.onTaskVanished(taskInfo);
284         if (mConfig.mShouldAutoRestartOnTaskRemoval && mCarTaskViewController.isHostVisible()) {
285             // onTaskVanished can be called when the host is in the background. In this case
286             // embedded activity should not be started.
287             Slogf.i(TAG, "Restarting task " + taskInfo.baseActivity
288                     + " in ControlledRemoteCarTaskView");
289             startActivity();
290         }
291         mCallbackExecutor.execute(() -> {
292             if (isReleased()) {
293                 Slogf.w(TAG, "car task view has already been released");
294                 return;
295             }
296             mCallback.onTaskVanished(taskInfo);
297         });
298     }
299 
300     /**
301      * @return the {@link android.app.ActivityManager.RunningTaskInfo} of the task currently
302      * running in the TaskView.
303      */
304     @MainThread
305     @Override
306     @Nullable
getTaskInfo()307     public ActivityManager.RunningTaskInfo getTaskInfo() {
308         return mTaskInfo;
309     }
310 
getConfig()311     ControlledRemoteCarTaskViewConfig getConfig() {
312         return mConfig;
313     }
314 
315     @Override
toString()316     public String toString() {
317         return toString(/* withBounds= */ false);
318     }
319 
toString(boolean withBounds)320     String toString(boolean withBounds) {
321         if (withBounds) {
322             ViewHelper.getBoundsOnScreen(this, mTmpRect);
323         }
324         return TAG + " {\n"
325                 + "  config=" + mConfig + "\n"
326                 + "  taskId=" + (getTaskInfo() == null ? "null" : getTaskInfo().taskId) + "\n"
327                 + (withBounds ? ("  boundsOnScreen=" + mTmpRect) : "")
328                 + "}\n";
329     }
330 
331     // Since SurfaceView is public, these methods need to be overridden. Details in b/296680464.
332     @Override
333     @RequiresPermission(Car.PERMISSION_REGISTER_CAR_SYSTEM_UI_PROXY)
334     @MainThread
addInsets(int index, int type, @NonNull Rect frame)335     public void addInsets(int index, int type, @NonNull Rect frame) {
336         super.addInsets(index, type, frame);
337     }
338 
339     @Override
340     @RequiresPermission(Car.PERMISSION_REGISTER_CAR_SYSTEM_UI_PROXY)
removeInsets(int index, int type)341     public void removeInsets(int index, int type) {
342         super.removeInsets(index, type);
343     }
344 
345     @Override
346     @MainThread
release()347     public void release() {
348         super.release();
349         stopTheStartActivityBackoffIfExists();
350     }
351 
352     @Override
onAttachedToWindow()353     public void onAttachedToWindow() {
354         super.onAttachedToWindow();
355     }
356 
357 
358     @Override
onDetachedFromWindow()359     public void onDetachedFromWindow() {
360         super.onDetachedFromWindow();
361     }
362 
363     @Override
364     @MainThread
isInitialized()365     public boolean isInitialized() {
366         return super.isInitialized();
367     }
368 
369     @Override
370     @MainThread
setObscuredTouchRegion(@onNull Region obscuredRegion)371     public void setObscuredTouchRegion(@NonNull Region obscuredRegion) {
372         super.setObscuredTouchRegion(obscuredRegion);
373     }
374 
375     @Override
376     @MainThread
setObscuredTouchRect(@onNull Rect obscuredRect)377     public void setObscuredTouchRect(@NonNull Rect obscuredRect) {
378         super.setObscuredTouchRect(obscuredRect);
379     }
380 
381     @Override
382     @RequiresPermission(Car.PERMISSION_REGISTER_CAR_SYSTEM_UI_PROXY)
383     @MainThread
updateWindowBounds()384     public void updateWindowBounds() {
385         super.updateWindowBounds();
386     }
387 }
388