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 /** 52 * A car launcher view model to manage the lifecycle of {@link RemoteCarTaskView}. 53 */ 54 public final class CarLauncherViewModel extends ViewModel implements DefaultLifecycleObserver { 55 private static final String TAG = CarLauncher.TAG; 56 private static final boolean DEBUG = CarLauncher.DEBUG; 57 private static final boolean sAutoRestartOnCrash = Build.IS_USER; 58 59 private final CarActivityManager mCarActivityManager; 60 private final Car mCar; 61 private final CarTaskViewControllerHostLifecycle mHostLifecycle; 62 @SuppressLint("StaticFieldLeak") // We're not leaking this context as it is the window context. 63 private final Context mWindowContext; 64 private final Intent mMapsIntent; 65 private final MutableLiveData<RemoteCarTaskView> mRemoteCarTaskView; 66 CarLauncherViewModel(@iContext Context context, @NonNull Intent mapsIntent)67 public CarLauncherViewModel(@UiContext Context context, @NonNull Intent mapsIntent) { 68 mWindowContext = context.createWindowContext(TYPE_APPLICATION_STARTING, /* options */ null); 69 mMapsIntent = mapsIntent; 70 mCar = Car.createCar(mWindowContext); 71 mCarActivityManager = mCar.getCarManager(CarActivityManager.class); 72 mHostLifecycle = new CarTaskViewControllerHostLifecycle(); 73 mRemoteCarTaskView = new MutableLiveData<>(null); 74 ControlledRemoteCarTaskViewCallback controlledRemoteCarTaskViewCallback = 75 new ControlledRemoteCarTaskViewCallbackImpl(mRemoteCarTaskView); 76 77 CarTaskViewControllerCallback carTaskViewControllerCallback = 78 new CarTaskViewControllerCallbackImpl(controlledRemoteCarTaskViewCallback); 79 80 mCarActivityManager.getCarTaskViewController(mWindowContext, mHostLifecycle, 81 mWindowContext.getMainExecutor(), carTaskViewControllerCallback); 82 } 83 getRemoteCarTaskView()84 LiveData<RemoteCarTaskView> getRemoteCarTaskView() { 85 return mRemoteCarTaskView; 86 } 87 88 /** 89 * Returns remote car task view task Id. 90 */ getRemoteCarTaskViewTaskId()91 public int getRemoteCarTaskViewTaskId() { 92 if (mRemoteCarTaskView != null && mRemoteCarTaskView.getValue() != null 93 && mRemoteCarTaskView.getValue().getTaskInfo() != null) { 94 return mRemoteCarTaskView.getValue().getTaskInfo().taskId; 95 } 96 return INVALID_TASK_ID; 97 } 98 99 /** 100 * Shows remote car task view when activity is resumed. 101 */ 102 @Override onResume(@onNull LifecycleOwner owner)103 public void onResume(@NonNull LifecycleOwner owner) { 104 DefaultLifecycleObserver.super.onResume(owner); 105 // Do not trigger 'hostAppeared()'}' in onResume. 106 // If the host Activity was hidden by an Activity, the Activity is moved to the other 107 // display, what the system expects would be the new moved Activity becomes the top one. 108 // But, at the time, the host Activity became visible and 'onResume()' is triggered. 109 // If 'hostAppeared()' is called in onResume, which moves the embeddedTask to the top and 110 // breaks the contract (the newly moved Activity becomes top). 111 // The contract is maintained by android.server.wm.multidisplay.MultiDisplayClientTests. 112 // BTW, if we don't invoke 'hostAppeared()', which makes the embedded task invisible if 113 // the host Activity gets the new Intent, so we'd call 'hostAppeared()' in onNewIntent. 114 } 115 116 @Override onStop(@onNull LifecycleOwner owner)117 public void onStop(@NonNull LifecycleOwner owner) { 118 DefaultLifecycleObserver.super.onStop(owner); 119 mHostLifecycle.hostDisappeared(); 120 } 121 122 @Override onCleared()123 protected void onCleared() { 124 if (mRemoteCarTaskView != null) { 125 mRemoteCarTaskView.setValue(null); 126 } 127 if (mCar != null) { 128 mCar.disconnect(); 129 } 130 mHostLifecycle.hostDestroyed(); 131 super.onCleared(); 132 } 133 getNewIntentListener()134 public Consumer<Intent> getNewIntentListener() { 135 return mNewIntentConsumer; 136 } 137 138 private final Consumer<Intent> mNewIntentConsumer = new Consumer<Intent>() { 139 @Override 140 public void accept(Intent intent) { 141 mHostLifecycle.hostAppeared(); 142 } 143 }; 144 145 private static final class ControlledRemoteCarTaskViewCallbackImpl implements 146 ControlledRemoteCarTaskViewCallback { 147 private final MutableLiveData<RemoteCarTaskView> mRemoteCarTaskView; 148 ControlledRemoteCarTaskViewCallbackImpl( MutableLiveData<RemoteCarTaskView> remoteCarTaskView)149 private ControlledRemoteCarTaskViewCallbackImpl( 150 MutableLiveData<RemoteCarTaskView> remoteCarTaskView) { 151 mRemoteCarTaskView = remoteCarTaskView; 152 } 153 154 @Override onTaskViewCreated(@onNull ControlledRemoteCarTaskView taskView)155 public void onTaskViewCreated(@NonNull ControlledRemoteCarTaskView taskView) { 156 mRemoteCarTaskView.setValue(taskView); 157 } 158 159 @Override onTaskViewInitialized()160 public void onTaskViewInitialized() { 161 if (DEBUG) { 162 Log.d(TAG, "MapsTaskView: onTaskViewInitialized"); 163 } 164 } 165 166 @Override onTaskAppeared(@onNull ActivityManager.RunningTaskInfo taskInfo)167 public void onTaskAppeared(@NonNull ActivityManager.RunningTaskInfo taskInfo) { 168 if (DEBUG) { 169 Log.d(TAG, "MapsTaskView: onTaskAppeared: taskId=" + taskInfo.taskId); 170 } 171 if (!sAutoRestartOnCrash) { 172 mRemoteCarTaskView.getValue().setBackgroundColor(Color.TRANSPARENT); 173 } 174 } 175 176 @Override onTaskVanished(@onNull ActivityManager.RunningTaskInfo taskInfo)177 public void onTaskVanished(@NonNull ActivityManager.RunningTaskInfo taskInfo) { 178 if (DEBUG) { 179 Log.d(TAG, "MapsTaskView: onTaskVanished: taskId=" + taskInfo.taskId); 180 } 181 if (!sAutoRestartOnCrash) { 182 // RemoteCarTaskView color is set to red to indicate 183 // that nothing is wrong with the task view but maps 184 // in the task view has crashed. More details in 185 // b/247156851. 186 mRemoteCarTaskView.getValue().setBackgroundColor(Color.RED); 187 } 188 } 189 } 190 191 private final class CarTaskViewControllerCallbackImpl implements CarTaskViewControllerCallback { 192 private final ControlledRemoteCarTaskViewCallback mControlledRemoteCarTaskViewCallback; 193 CarTaskViewControllerCallbackImpl( ControlledRemoteCarTaskViewCallback controlledRemoteCarTaskViewCallback)194 private CarTaskViewControllerCallbackImpl( 195 ControlledRemoteCarTaskViewCallback controlledRemoteCarTaskViewCallback) { 196 mControlledRemoteCarTaskViewCallback = controlledRemoteCarTaskViewCallback; 197 } 198 199 @Override onConnected(@onNull CarTaskViewController carTaskViewController)200 public void onConnected(@NonNull CarTaskViewController carTaskViewController) { 201 carTaskViewController.createControlledRemoteCarTaskView( 202 new ControlledRemoteCarTaskViewConfig.Builder() 203 .setActivityIntent(mMapsIntent) 204 .setShouldAutoRestartOnTaskRemoval(sAutoRestartOnCrash) 205 .build(), 206 mWindowContext.getMainExecutor(), 207 mControlledRemoteCarTaskViewCallback); 208 } 209 210 @Override onDisconnected(@onNull CarTaskViewController carTaskViewController)211 public void onDisconnected(@NonNull CarTaskViewController carTaskViewController) { 212 if (DEBUG) { 213 Log.d(TAG, "onDisconnected"); 214 } 215 mRemoteCarTaskView.setValue(null); 216 } 217 } 218 219 static final class CarLauncherViewModelFactory implements ViewModelProvider.Factory { 220 private final Context mContext; 221 private final Intent mMapsIntent; 222 CarLauncherViewModelFactory(@iContext Context context, @NonNull Intent mapsIntent)223 CarLauncherViewModelFactory(@UiContext Context context, @NonNull Intent mapsIntent) { 224 mMapsIntent = requireNonNull(mapsIntent); 225 mContext = requireNonNull(context); 226 } 227 228 @NonNull 229 @Override create(Class<T> modelClass)230 public <T extends ViewModel> T create(Class<T> modelClass) { 231 return modelClass.cast(new CarLauncherViewModel(mContext, mMapsIntent)); 232 } 233 } 234 } 235