1 /* 2 * Copyright (C) 2018 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.PRIVATE_FLAG_TRUSTED_OVERLAY; 21 22 import android.app.ActivityManager; 23 import android.app.TaskStackListener; 24 import android.car.user.CarUserManager; 25 import android.content.Intent; 26 import android.content.res.Configuration; 27 import android.os.Bundle; 28 import android.util.Log; 29 import android.view.ViewGroup; 30 import android.view.WindowManager; 31 32 import androidx.collection.ArraySet; 33 import androidx.fragment.app.FragmentActivity; 34 import androidx.fragment.app.FragmentTransaction; 35 import androidx.lifecycle.ViewModelProvider; 36 37 import com.android.car.carlauncher.homescreen.HomeCardModule; 38 import com.android.car.carlauncher.taskstack.TaskStackChangeListeners; 39 import com.android.car.internal.common.UserHelperLite; 40 import com.android.wm.shell.TaskView; 41 import com.android.wm.shell.common.HandlerExecutor; 42 43 import com.google.common.annotations.VisibleForTesting; 44 45 import java.util.Set; 46 47 /** 48 * Basic Launcher for Android Automotive which demonstrates the use of {@link TaskView} to host 49 * maps content and uses a Model-View-Presenter structure to display content in cards. 50 * 51 * <p>Implementations of the Launcher that use the given layout of the main activity 52 * (car_launcher.xml) can customize the home screen cards by providing their own 53 * {@link HomeCardModule} for R.id.top_card or R.id.bottom_card. Otherwise, implementations that 54 * use their own layout should define their own activity rather than using this one. 55 * 56 * <p>Note: On some devices, the TaskView may render with a width, height, and/or aspect 57 * ratio that does not meet Android compatibility definitions. Developers should work with content 58 * owners to ensure content renders correctly when extending or emulating this class. 59 */ 60 public class CarLauncher extends FragmentActivity { 61 public static final String TAG = "CarLauncher"; 62 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 63 64 private ActivityManager mActivityManager; 65 private TaskViewManager mTaskViewManager; 66 67 private CarTaskView mTaskView; 68 private int mCarLauncherTaskId = INVALID_TASK_ID; 69 private Set<HomeCardModule> mHomeCardModules; 70 71 /** Set to {@code true} once we've logged that the Activity is fully drawn. */ 72 private boolean mIsReadyLogged; 73 private boolean mUseSmallCanvasOptimizedMap; 74 75 private final TaskStackListener mTaskStackListener = new TaskStackListener() { 76 @Override 77 public void onTaskFocusChanged(int taskId, boolean focused) {} 78 79 @Override 80 public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task, 81 boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) { 82 if (DEBUG) { 83 Log.d(TAG, "onActivityRestartAttempt: taskId=" + task.taskId 84 + ", homeTaskVisible=" + homeTaskVisible + ", wasVisible=" + wasVisible); 85 } 86 if (!mUseSmallCanvasOptimizedMap 87 && !homeTaskVisible 88 && mTaskView != null 89 && mTaskView.getTaskId() == task.taskId) { 90 // The embedded map component received an intent, therefore forcibly bringing the 91 // launcher to the foreground. 92 bringToForeground(); 93 return; 94 } 95 } 96 }; 97 98 @VisibleForTesting setCarUserManager(CarUserManager carUserManager)99 void setCarUserManager(CarUserManager carUserManager) { 100 if (mTaskViewManager == null) { 101 Log.w(TAG, "Task view manager is null, cannot set CarUserManager"); 102 return; 103 } 104 mTaskViewManager.setCarUserManager(carUserManager); 105 } 106 107 @Override onCreate(Bundle savedInstanceState)108 protected void onCreate(Bundle savedInstanceState) { 109 super.onCreate(savedInstanceState); 110 111 if (CarLauncherUtils.isCustomDisplayPolicyDefined(this)) { 112 Intent controlBarIntent = new Intent(this, ControlBarActivity.class); 113 controlBarIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 114 startActivity(controlBarIntent); 115 startActivity( 116 CarLauncherUtils.getMapsIntent(this).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); 117 setContentView(R.layout.car_launcher); 118 return; 119 } 120 121 mUseSmallCanvasOptimizedMap = 122 CarLauncherUtils.isSmallCanvasOptimizedMapIntentConfigured(this); 123 124 mActivityManager = getSystemService(ActivityManager.class); 125 mCarLauncherTaskId = getTaskId(); 126 TaskStackChangeListeners.getInstance().registerTaskStackListener(mTaskStackListener); 127 128 // Setting as trusted overlay to let touches pass through. 129 getWindow().addPrivateFlags(PRIVATE_FLAG_TRUSTED_OVERLAY); 130 // To pass touches to the underneath task. 131 getWindow().addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL); 132 133 // Don't show the maps panel in multi window mode. 134 // NOTE: CTS tests for split screen are not compatible with activity views on the default 135 // activity of the launcher 136 if (isInMultiWindowMode() || isInPictureInPictureMode()) { 137 setContentView(R.layout.car_launcher_multiwindow); 138 } else { 139 setContentView(R.layout.car_launcher); 140 // We don't want to show Map card unnecessarily for the headless user 0. 141 if (!UserHelperLite.isHeadlessSystemUser(getUserId())) { 142 ViewGroup mapsCard = findViewById(R.id.maps_card); 143 if (mapsCard != null) { 144 setUpTaskView(mapsCard); 145 } 146 } 147 } 148 initializeCards(); 149 150 } 151 setUpTaskView(ViewGroup parent)152 private void setUpTaskView(ViewGroup parent) { 153 Set<String> taskViewPackages = new ArraySet<>(getResources().getStringArray( 154 R.array.config_taskViewPackages)); 155 mTaskViewManager = new TaskViewManager(this, 156 new HandlerExecutor(getMainThreadHandler())); 157 158 Intent mapIntent = mUseSmallCanvasOptimizedMap 159 ? CarLauncherUtils.getSmallCanvasOptimizedMapIntent(this) 160 : CarLauncherUtils.getMapsIntent(this); 161 // Don't want to show this Activity in Recents. 162 mapIntent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); 163 mTaskViewManager.createControlledCarTaskView( 164 getMainExecutor(), 165 ControlledCarTaskViewConfig.builder() 166 .setActivityIntent(mapIntent) 167 // TODO(b/263876526): Enable auto restart after ensuring no CTS failure. 168 .setAutoRestartOnCrash(false) 169 .build(), 170 new ControlledCarTaskViewCallbacks() { 171 @Override 172 public void onTaskViewCreated(CarTaskView taskView) { 173 parent.addView(taskView); 174 mTaskView = taskView; 175 } 176 177 @Override 178 public void onTaskViewReady() { 179 maybeLogReady(); 180 } 181 182 @Override 183 public Set<String> getDependingPackageNames() { 184 return taskViewPackages; 185 } 186 }); 187 } 188 189 @Override onResume()190 protected void onResume() { 191 super.onResume(); 192 maybeLogReady(); 193 } 194 195 @Override onDestroy()196 protected void onDestroy() { 197 super.onDestroy(); 198 if (CarLauncherUtils.isCustomDisplayPolicyDefined(this)) { 199 return; 200 } 201 TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mTaskStackListener); 202 release(); 203 } 204 release()205 private void release() { 206 mTaskView = null; 207 } 208 209 @Override onConfigurationChanged(Configuration newConfig)210 public void onConfigurationChanged(Configuration newConfig) { 211 super.onConfigurationChanged(newConfig); 212 if (CarLauncherUtils.isCustomDisplayPolicyDefined(this)) { 213 return; 214 } 215 initializeCards(); 216 } 217 initializeCards()218 private void initializeCards() { 219 if (mHomeCardModules == null) { 220 mHomeCardModules = new ArraySet<>(); 221 for (String providerClassName : getResources().getStringArray( 222 R.array.config_homeCardModuleClasses)) { 223 try { 224 long reflectionStartTime = System.currentTimeMillis(); 225 HomeCardModule cardModule = (HomeCardModule) Class.forName( 226 providerClassName).newInstance(); 227 cardModule.setViewModelProvider(new ViewModelProvider( /* owner= */this)); 228 mHomeCardModules.add(cardModule); 229 if (DEBUG) { 230 long reflectionTime = System.currentTimeMillis() - reflectionStartTime; 231 Log.d(TAG, "Initialization of HomeCardModule class " + providerClassName 232 + " took " + reflectionTime + " ms"); 233 } 234 } catch (IllegalAccessException | InstantiationException | 235 ClassNotFoundException e) { 236 Log.w(TAG, "Unable to create HomeCardProvider class " + providerClassName, e); 237 } 238 } 239 } 240 FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); 241 for (HomeCardModule cardModule : mHomeCardModules) { 242 transaction.replace(cardModule.getCardResId(), cardModule.getCardView()); 243 } 244 transaction.commitNow(); 245 } 246 247 /** Logs that the Activity is ready. Used for startup time diagnostics. */ maybeLogReady()248 private void maybeLogReady() { 249 boolean isResumed = isResumed(); 250 boolean taskViewInitialized = mTaskView != null && mTaskView.isInitialized(); 251 if (DEBUG) { 252 Log.d(TAG, "maybeLogReady(" + getUserId() + "): mapsReady=" 253 + taskViewInitialized + ", started=" + isResumed + ", alreadyLogged: " 254 + mIsReadyLogged); 255 } 256 if (taskViewInitialized && isResumed) { 257 // We should report every time - the Android framework will take care of logging just 258 // when it's effectively drawn for the first time, but.... 259 reportFullyDrawn(); 260 if (!mIsReadyLogged) { 261 // ... we want to manually check that the Log.i below (which is useful to show 262 // the user id) is only logged once (otherwise it would be logged every time the 263 // user taps Home) 264 Log.i(TAG, "Launcher for user " + getUserId() + " is ready"); 265 mIsReadyLogged = true; 266 } 267 } 268 } 269 270 /** Brings the Car Launcher to the foreground. */ bringToForeground()271 private void bringToForeground() { 272 if (mCarLauncherTaskId != INVALID_TASK_ID) { 273 mActivityManager.moveTaskToFront(mCarLauncherTaskId, /* flags= */ 0); 274 } 275 } 276 } 277