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 package android.car.app;
17 
18 import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_UNLOCKED;
19 
20 import android.annotation.MainThread;
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.app.Activity;
24 import android.car.app.CarTaskViewControllerHostLifecycle.CarTaskViewControllerHostLifecycleObserver;
25 import android.car.builtin.app.ActivityManagerHelper;
26 import android.car.builtin.util.Slogf;
27 import android.car.user.CarUserManager;
28 import android.car.user.CarUserManager.UserLifecycleListener;
29 import android.car.user.UserLifecycleEventFilter;
30 import android.content.Context;
31 import android.os.Binder;
32 import android.os.IBinder;
33 import android.os.RemoteException;
34 import android.util.ArrayMap;
35 
36 import com.android.internal.annotations.GuardedBy;
37 
38 import java.util.concurrent.Executor;
39 
40 /**
41  * This class is responsible to create and manage the {@link CarTaskViewController} instances.
42  * - It connects to the {@link android.car.app.CarSystemUIProxy} & listens to the {@link Activity}'s
43  * lifecycle.
44  * - It is also responsible to dispatch {@link CarTaskViewControllerCallback} methods to the
45  * clients.
46  */
47 final class CarTaskViewControllerSupervisor {
48     private static final String TAG = CarTaskViewControllerSupervisor.class.getSimpleName();
49     private final ArrayMap<CarTaskViewControllerHostLifecycle, ActivityHolder> mActivityHolders =
50             new ArrayMap<>();
51     private final ICarActivityService mCarActivityService;
52     private final Executor mMainExecutor;
53 
54     @Nullable private ICarSystemUIProxyCallback mSystemUIProxyCallback = null;
55     @Nullable private ICarSystemUIProxy mICarSystemUI = null;
56 
57     private final IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
58         @Override
59         public void binderDied() {
60             long identity = Binder.clearCallingIdentity();
61             try {
62                 mMainExecutor.execute(() -> onSystemUIProxyDisconnected());
63             } finally {
64                 Binder.restoreCallingIdentity(identity);
65             }
66         }
67     };
68 
69     private final CarTaskViewControllerHostLifecycleObserver
70             mCarTaskViewControllerHostLifecycleObserver =
71             new CarTaskViewControllerHostLifecycleObserver() {
72                 public void onHostAppeared(CarTaskViewControllerHostLifecycle lifecycle) {
73                     mActivityHolders.get(lifecycle).maybeShowControlledTasks();
74                 }
75 
76                 @Override
77                 public void onHostDisappeared(CarTaskViewControllerHostLifecycle lifecycle) {
78                 }
79 
80                 @Override
81                 public void onHostDestroyed(CarTaskViewControllerHostLifecycle lifecycle) {
82                     lifecycle.unregisterObserver(this);
83 
84                     ActivityHolder activityHolder = mActivityHolders.remove(lifecycle);
85                     activityHolder.onActivityDestroyed();
86 
87                     // When all the underlying activities are destroyed, the callback should be
88                     // removed from the CarActivityService as it's no longer required.
89                     // A new callback will be registered when a new activity calls the
90                     // createTaskViewController.
91                     if (mActivityHolders.isEmpty()) {
92                         try {
93                             mCarActivityService.removeCarSystemUIProxyCallback(
94                                     mSystemUIProxyCallback);
95                             mSystemUIProxyCallback = null;
96                         } catch (RemoteException e) {
97                             Slogf.e(TAG, "Failed to remove CarSystemUIProxyCallback", e);
98                         }
99                     }
100                 }
101             };
102 
103     /**
104      * @param carActivityService the handle to the {@link com.android.car.am.CarActivityService}.
105      */
CarTaskViewControllerSupervisor(ICarActivityService carActivityService, Executor mainExecutor, @NonNull CarUserManager carUserManager)106     CarTaskViewControllerSupervisor(ICarActivityService carActivityService, Executor mainExecutor,
107             @NonNull CarUserManager carUserManager) {
108         mCarActivityService = carActivityService;
109         mMainExecutor = mainExecutor;
110         UserLifecycleEventFilter filter = new UserLifecycleEventFilter.Builder()
111                 .addEventType(USER_LIFECYCLE_EVENT_TYPE_UNLOCKED).build();
112         carUserManager.addListener(mainExecutor, filter, mUserLifecycleListener);
113     }
114 
getToken(Activity activity)115     private static IBinder getToken(Activity activity) {
116         return ActivityManagerHelper.getActivityToken(activity);
117     }
118 
119     /**
120      * Creates a new {@link CarTaskViewController} instance for the provided {@code hostActivity}.
121      *
122      * @param callbackExecutor the executor which the {@code carTaskViewControllerCallback} methods
123      *                         will be called upon.
124      * @param carTaskViewControllerCallback the life callback methods for the
125      *                                    {@link CarTaskViewController}.
126      * @param hostActivity the activity which will be hosting the taskviews that will be created
127      *                     using the underlying {@link CarTaskViewController}.
128      * @throws RemoteException as thrown by
129      * {@link ICarSystemUIProxy#createCarTaskView(CarTaskViewClient)}.
130      */
131     @MainThread
createCarTaskViewController( Context context, @NonNull CarTaskViewControllerHostLifecycle hostActivity, @NonNull Executor callbackExecutor, @NonNull CarTaskViewControllerCallback carTaskViewControllerCallback)132     void createCarTaskViewController(
133             Context context,
134             @NonNull CarTaskViewControllerHostLifecycle hostActivity,
135             @NonNull Executor callbackExecutor,
136             @NonNull CarTaskViewControllerCallback carTaskViewControllerCallback)
137             throws RemoteException {
138         if (mActivityHolders.containsKey(hostActivity)) {
139             throw new IllegalArgumentException("A CarTaskViewController already exists for this "
140                     + "activity. Cannot create another one.");
141         }
142         hostActivity.registerObserver(mCarTaskViewControllerHostLifecycleObserver);
143         ActivityHolder activityHolder = new ActivityHolder(context, hostActivity, callbackExecutor,
144                 carTaskViewControllerCallback, mCarActivityService);
145         mActivityHolders.put(hostActivity, activityHolder);
146 
147         if (mSystemUIProxyCallback != null && mICarSystemUI != null) {
148             // If there is already a connection with the CarSystemUIProxy, trigger onConnected
149             // right away.
150             activityHolder.onCarSystemUIConnected(mICarSystemUI);
151             return;
152         }
153         if (mSystemUIProxyCallback != null) {
154             // If there is no connection, but callback is registered, do nothing; as when
155             // connection is made, it will automatically trigger onConnected for all the  activity
156             // holders.
157             Slogf.d(TAG, "SystemUIProxyCallback already registered but not connected yet.");
158             return;
159         }
160 
161         // If the CarSystemUIProxyCallback is not registered, register it now.
162         mSystemUIProxyCallback = new ICarSystemUIProxyCallback.Stub() {
163             @Override
164             public void onConnected(ICarSystemUIProxy carSystemUIProxy) {
165                 long identity = Binder.clearCallingIdentity();
166                 try {
167                     mMainExecutor.execute(() -> onSystemUIProxyConnected(carSystemUIProxy));
168                 } finally {
169                     Binder.restoreCallingIdentity(identity);
170                 }
171             }
172         };
173         try {
174             mCarActivityService.addCarSystemUIProxyCallback(mSystemUIProxyCallback);
175         } catch (RemoteException e) {
176             mSystemUIProxyCallback = null;
177             throw e;
178         }
179     }
180 
181     @MainThread
onSystemUIProxyConnected(ICarSystemUIProxy systemUIProxy)182     private void onSystemUIProxyConnected(ICarSystemUIProxy systemUIProxy) {
183         mICarSystemUI = systemUIProxy;
184         try {
185             systemUIProxy.asBinder().linkToDeath(mDeathRecipient, /* flags= */ 0);
186         } catch (RemoteException ex) {
187             throw new IllegalStateException("Linking to binder death failed for "
188                     + "ICarSystemUIProxy, the System UI might already died", ex);
189         }
190 
191         for (ActivityHolder activityHolder : mActivityHolders.values()) {
192             activityHolder.onCarSystemUIConnected(systemUIProxy);
193         }
194     }
195 
196     @MainThread
onSystemUIProxyDisconnected()197     private void onSystemUIProxyDisconnected() {
198         mICarSystemUI.asBinder().unlinkToDeath(mDeathRecipient, /* flags= */ 0);
199         mICarSystemUI = null;
200 
201         for (ActivityHolder activityHolder : mActivityHolders.values()) {
202             activityHolder.onCarSystemUIDisconnected();
203         }
204         // No need to remove the holders as activities are still active and will create the
205         // taskviews again, when system ui will be connected again.
206     }
207 
208     private static final class ActivityHolder {
209         private final Context mContext;
210         private final CarTaskViewControllerHostLifecycle mActivity;
211         private final Executor mCallbackExecutor;
212         private final CarTaskViewControllerCallback mCarTaskViewControllerCallback;
213         private final ICarActivityService mCarActivityService;
214         private final Object mLock = new Object();
215 
216         @GuardedBy("mLock")
217         private CarTaskViewController mCarTaskViewController;
218 
ActivityHolder(Context context, CarTaskViewControllerHostLifecycle activity, Executor callbackExecutor, CarTaskViewControllerCallback carTaskViewControllerCallback, ICarActivityService carActivityService)219         private ActivityHolder(Context context,
220                 CarTaskViewControllerHostLifecycle activity,
221                 Executor callbackExecutor,
222                 CarTaskViewControllerCallback carTaskViewControllerCallback,
223                 ICarActivityService carActivityService) {
224             mContext = context;
225             mActivity = activity;
226             mCallbackExecutor = callbackExecutor;
227             mCarTaskViewControllerCallback = carTaskViewControllerCallback;
228             mCarActivityService = carActivityService;
229         }
230 
maybeShowControlledTasks()231         private void maybeShowControlledTasks() {
232             synchronized (mLock) {
233                 if (mCarTaskViewController == null || !mCarTaskViewController.isHostVisible()) {
234                     return;
235                 }
236                 mCarTaskViewController.showEmbeddedControlledTasks();
237             }
238         }
239 
onCarSystemUIConnected(ICarSystemUIProxy systemUIProxy)240         private void onCarSystemUIConnected(ICarSystemUIProxy systemUIProxy) {
241             synchronized (mLock) {
242                 mCarTaskViewController =
243                         new CarTaskViewController(mContext, mActivity, systemUIProxy,
244                                 mCarActivityService);
245             }
246             mCallbackExecutor.execute(() -> {
247                 synchronized (mLock) {
248                     // Check for null because the mCarTaskViewController might have already been
249                     // released but this code path is executed later because the executor was
250                     // busy.
251                     if (mCarTaskViewController == null) {
252                         Slogf.w(TAG,
253                                 "car task view controller not found when triggering callback, not"
254                                         + " dispatching onConnected");
255                         return;
256                     }
257                     mCarTaskViewControllerCallback.onConnected(mCarTaskViewController);
258                 }
259             });
260         }
261 
onCarSystemUIDisconnected()262         private void onCarSystemUIDisconnected() {
263             synchronized (mLock) {
264                 if (mCarTaskViewController == null) {
265                     Slogf.w(TAG,
266                             "car task view controller not found, not dispatching onDisconnected");
267                     return;
268                 }
269                 // Only release the taskviews and not the controller because the system ui might get
270                 // connected while the activity is still visible.
271                 mCarTaskViewController.releaseTaskViews();
272             }
273             mCallbackExecutor.execute(() -> {
274                 synchronized (mLock) {
275                     if (mCarTaskViewController == null) {
276                         Slogf.w(TAG, "car task view controller not found when triggering "
277                                 + "callback, not dispatching onDisconnected");
278                         return;
279                     }
280                     mCarTaskViewControllerCallback.onDisconnected(mCarTaskViewController);
281                 }
282             });
283         }
284 
onActivityDestroyed()285         private void onActivityDestroyed() {
286             releaseController();
287         }
288 
releaseController()289         private void releaseController() {
290             synchronized (mLock) {
291                 if (mCarTaskViewController == null) {
292                     Slogf.w(TAG, "car task view controller not found, not releasing");
293                     return;
294                 }
295                 mCarTaskViewController.release();
296                 mCarTaskViewController = null;
297             }
298         }
299     }
300 
301     private UserLifecycleListener mUserLifecycleListener = new UserLifecycleListener() {
302         @Override
303         public void onEvent(@NonNull CarUserManager.UserLifecycleEvent event) {
304             // Only called when USER_LIFECYCLE_EVENT_TYPE_UNLOCKED.
305             for (int i = mActivityHolders.size() - 1; i >= 0; --i) {
306                 ActivityHolder activityHolder = mActivityHolders.valueAt(i);
307                 activityHolder.maybeShowControlledTasks();
308             }
309         }
310     };
311 }
312