• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 com.android.car.carlauncher;
18 
19 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
20 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
21 
22 import static java.util.Objects.requireNonNull;
23 
24 import android.annotation.NonNull;
25 import android.annotation.SuppressLint;
26 import android.annotation.UiContext;
27 import android.app.ActivityManager;
28 import android.car.Car;
29 import android.car.app.CarActivityManager;
30 import android.car.app.CarTaskViewController;
31 import android.car.app.CarTaskViewControllerCallback;
32 import android.car.app.CarTaskViewControllerHostLifecycle;
33 import android.car.app.ControlledRemoteCarTaskView;
34 import android.car.app.ControlledRemoteCarTaskViewCallback;
35 import android.car.app.ControlledRemoteCarTaskViewConfig;
36 import android.car.app.RemoteCarTaskView;
37 import android.content.Context;
38 import android.content.Intent;
39 import android.graphics.Color;
40 import android.os.Build;
41 import android.util.Log;
42 
43 import androidx.core.util.Consumer;
44 import androidx.lifecycle.DefaultLifecycleObserver;
45 import androidx.lifecycle.LifecycleOwner;
46 import androidx.lifecycle.LiveData;
47 import androidx.lifecycle.MutableLiveData;
48 import androidx.lifecycle.ViewModel;
49 import androidx.lifecycle.ViewModelProvider;
50 
51 import com.google.common.annotations.VisibleForTesting;
52 
53 /**
54  * A car launcher view model to manage the lifecycle of {@link RemoteCarTaskView}.
55  */
56 public final class CarLauncherViewModel extends ViewModel implements DefaultLifecycleObserver {
57     private static final String TAG = CarLauncher.TAG;
58     private static final boolean DEBUG = CarLauncher.DEBUG;
59     private static final boolean sAutoRestartOnCrash = Build.IS_USER;
60 
61     private final CarActivityManager mCarActivityManager;
62     private final Car mCar;
63     @SuppressLint("StaticFieldLeak") // We're not leaking this context as it is the window context.
64     private final Context mWindowContext;
65 
66     // Do not make this final because the maps intent can be changed based on the state of TOS.
67     private Intent mMapsIntent;
68     private CarTaskViewControllerHostLifecycle mHostLifecycle;
69     private MutableLiveData<RemoteCarTaskView> mRemoteCarTaskView;
70 
CarLauncherViewModel(@iContext Context context, Intent mapsIntent)71     public CarLauncherViewModel(@UiContext Context context, Intent mapsIntent) {
72         mWindowContext = context.createWindowContext(TYPE_APPLICATION_STARTING, /* options */ null);
73         mCar = Car.createCar(mWindowContext);
74         mCarActivityManager = mCar.getCarManager(CarActivityManager.class);
75         initializeRemoteCarTaskView(mapsIntent);
76     }
77 
78     /**
79      * Initialize the remote car task view with the maps intent.
80      */
initializeRemoteCarTaskView(@onNull Intent mapsIntent)81     public void initializeRemoteCarTaskView(@NonNull Intent mapsIntent) {
82         if (DEBUG) {
83             Log.d(TAG, "Maps intent in the task view = " + mapsIntent.getComponent());
84         }
85         mMapsIntent = mapsIntent;
86         if (mRemoteCarTaskView != null && mRemoteCarTaskView.getValue() != null) {
87             // Release the remote car task view instance if it exists since otherwise there could
88             // be a memory leak
89             mRemoteCarTaskView.getValue().release();
90         }
91         mRemoteCarTaskView = new MutableLiveData<>(/* value= */ null);
92         mHostLifecycle = new CarTaskViewControllerHostLifecycle();
93         ControlledRemoteCarTaskViewCallback controlledRemoteCarTaskViewCallback =
94                 new ControlledRemoteCarTaskViewCallbackImpl(mRemoteCarTaskView);
95 
96         CarTaskViewControllerCallback carTaskViewControllerCallback =
97                 new CarTaskViewControllerCallbackImpl(controlledRemoteCarTaskViewCallback);
98 
99         mCarActivityManager.getCarTaskViewController(mWindowContext, mHostLifecycle,
100                 mWindowContext.getMainExecutor(), carTaskViewControllerCallback);
101     }
102 
getRemoteCarTaskView()103     LiveData<RemoteCarTaskView> getRemoteCarTaskView() {
104         return mRemoteCarTaskView;
105     }
106 
107     @VisibleForTesting
getMapsIntent()108     Intent getMapsIntent() {
109         return mMapsIntent;
110     }
111 
112     /**
113      * Returns remote car task view task Id.
114      */
getRemoteCarTaskViewTaskId()115     public int getRemoteCarTaskViewTaskId() {
116         if (mRemoteCarTaskView != null && mRemoteCarTaskView.getValue() != null
117                 && mRemoteCarTaskView.getValue().getTaskInfo() != null) {
118             return mRemoteCarTaskView.getValue().getTaskInfo().taskId;
119         }
120         return INVALID_TASK_ID;
121     }
122 
123     /**
124      * Shows remote car task view when activity is resumed.
125      */
126     @Override
onResume(@onNull LifecycleOwner owner)127     public void onResume(@NonNull LifecycleOwner owner) {
128         DefaultLifecycleObserver.super.onResume(owner);
129         // Do not trigger 'hostAppeared()' in onResume.
130         // If the host Activity was hidden by an Activity, the Activity is moved to the other
131         // display, what the system expects would be the new moved Activity becomes the top one.
132         // But, at the time, the host Activity became visible and 'onResume()' is triggered.
133         // If 'hostAppeared()' is called in onResume, which moves the embeddedTask to the top and
134         // breaks the contract (the newly moved Activity becomes top).
135         // The contract is maintained by android.server.wm.multidisplay.MultiDisplayClientTests.
136         // BTW, if we don't invoke 'hostAppeared()', which makes the embedded task invisible if
137         // the host Activity gets the new Intent, so we'd call 'hostAppeared()' in onNewIntent.
138     }
139 
140     @Override
onStop(@onNull LifecycleOwner owner)141     public void onStop(@NonNull LifecycleOwner owner) {
142         DefaultLifecycleObserver.super.onStop(owner);
143         mHostLifecycle.hostDisappeared();
144     }
145 
146     @Override
onCleared()147     protected void onCleared() {
148         if (mRemoteCarTaskView != null) {
149             mRemoteCarTaskView.setValue(null);
150         }
151         if (mCar != null) {
152             mCar.disconnect();
153         }
154         mHostLifecycle.hostDestroyed();
155         super.onCleared();
156     }
157 
getNewIntentListener()158     public Consumer<Intent> getNewIntentListener() {
159         return mNewIntentConsumer;
160     }
161 
162     private final Consumer<Intent> mNewIntentConsumer = new Consumer<Intent>() {
163         @Override
164         public void accept(Intent intent) {
165             mHostLifecycle.hostAppeared();
166         }
167     };
168 
169     private static final class ControlledRemoteCarTaskViewCallbackImpl implements
170             ControlledRemoteCarTaskViewCallback {
171         private final MutableLiveData<RemoteCarTaskView> mRemoteCarTaskView;
172 
ControlledRemoteCarTaskViewCallbackImpl( MutableLiveData<RemoteCarTaskView> remoteCarTaskView)173         private ControlledRemoteCarTaskViewCallbackImpl(
174                 MutableLiveData<RemoteCarTaskView> remoteCarTaskView) {
175             mRemoteCarTaskView = remoteCarTaskView;
176         }
177 
178         @Override
onTaskViewCreated(@onNull ControlledRemoteCarTaskView taskView)179         public void onTaskViewCreated(@NonNull ControlledRemoteCarTaskView taskView) {
180             if (DEBUG) {
181                 Log.d(TAG, "MapsTaskView: onTaskViewCreated");
182             }
183             mRemoteCarTaskView.setValue(taskView);
184         }
185 
186         @Override
onTaskViewInitialized()187         public void onTaskViewInitialized() {
188             if (DEBUG) {
189                 Log.d(TAG, "MapsTaskView: onTaskViewInitialized");
190             }
191         }
192 
193         @Override
onTaskAppeared(@onNull ActivityManager.RunningTaskInfo taskInfo)194         public void onTaskAppeared(@NonNull ActivityManager.RunningTaskInfo taskInfo) {
195             if (DEBUG) {
196                 Log.d(TAG, "MapsTaskView: onTaskAppeared: taskId=" + taskInfo.taskId);
197             }
198             if (!sAutoRestartOnCrash) {
199                 mRemoteCarTaskView.getValue().setBackgroundColor(Color.TRANSPARENT);
200             }
201         }
202 
203         @Override
onTaskVanished(@onNull ActivityManager.RunningTaskInfo taskInfo)204         public void onTaskVanished(@NonNull ActivityManager.RunningTaskInfo taskInfo) {
205             if (DEBUG) {
206                 Log.d(TAG, "MapsTaskView: onTaskVanished: taskId=" + taskInfo.taskId);
207             }
208             if (!sAutoRestartOnCrash) {
209                 // RemoteCarTaskView color is set to red to indicate
210                 // that nothing is wrong with the task view but maps
211                 // in the task view has crashed. More details in
212                 // b/247156851.
213                 mRemoteCarTaskView.getValue().setBackgroundColor(Color.RED);
214             }
215         }
216     }
217 
218     private final class CarTaskViewControllerCallbackImpl implements CarTaskViewControllerCallback {
219         private final ControlledRemoteCarTaskViewCallback mControlledRemoteCarTaskViewCallback;
220 
CarTaskViewControllerCallbackImpl( ControlledRemoteCarTaskViewCallback controlledRemoteCarTaskViewCallback)221         private CarTaskViewControllerCallbackImpl(
222                 ControlledRemoteCarTaskViewCallback controlledRemoteCarTaskViewCallback) {
223             mControlledRemoteCarTaskViewCallback = controlledRemoteCarTaskViewCallback;
224         }
225 
226         @Override
onConnected(@onNull CarTaskViewController carTaskViewController)227         public void onConnected(@NonNull CarTaskViewController carTaskViewController) {
228             carTaskViewController.createControlledRemoteCarTaskView(
229                     new ControlledRemoteCarTaskViewConfig.Builder()
230                             .setActivityIntent(mMapsIntent)
231                             .setShouldAutoRestartOnTaskRemoval(sAutoRestartOnCrash)
232                             .build(),
233                     mWindowContext.getMainExecutor(),
234                     mControlledRemoteCarTaskViewCallback);
235         }
236 
237         @Override
onDisconnected(@onNull CarTaskViewController carTaskViewController)238         public void onDisconnected(@NonNull CarTaskViewController carTaskViewController) {
239             mRemoteCarTaskView.setValue(null);
240         }
241     }
242 
243     static final class CarLauncherViewModelFactory implements ViewModelProvider.Factory {
244         private final Context mContext;
245         private final Intent mMapsIntent;
246 
CarLauncherViewModelFactory(@iContext Context context, @NonNull Intent mapsIntent)247         CarLauncherViewModelFactory(@UiContext Context context, @NonNull Intent mapsIntent) {
248             mMapsIntent = requireNonNull(mapsIntent);
249             mContext = requireNonNull(context);
250         }
251 
252         @NonNull
253         @Override
create(Class<T> modelClass)254         public <T extends ViewModel> T create(Class<T> modelClass) {
255             return modelClass.cast(new CarLauncherViewModel(mContext, mMapsIntent));
256         }
257     }
258 }
259