• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.ActivityOptions;
24 import android.app.TaskStackListener;
25 import android.car.Car;
26 import android.car.app.CarActivityManager;
27 import android.car.app.CarTaskViewController;
28 import android.car.app.CarTaskViewControllerCallback;
29 import android.car.app.ControlledRemoteCarTaskView;
30 import android.car.app.ControlledRemoteCarTaskViewCallback;
31 import android.car.app.ControlledRemoteCarTaskViewConfig;
32 import android.car.user.CarUserManager;
33 import android.content.ComponentName;
34 import android.content.Intent;
35 import android.content.res.Configuration;
36 import android.os.Bundle;
37 import android.os.UserManager;
38 import android.util.Log;
39 import android.view.Display;
40 import android.view.ViewGroup;
41 import android.view.WindowManager;
42 
43 import androidx.collection.ArraySet;
44 import androidx.fragment.app.FragmentActivity;
45 import androidx.fragment.app.FragmentTransaction;
46 import androidx.lifecycle.ViewModelProvider;
47 
48 import com.android.car.carlauncher.homescreen.HomeCardModule;
49 import com.android.car.carlauncher.taskstack.TaskStackChangeListeners;
50 import com.android.car.internal.common.UserHelperLite;
51 import com.android.wm.shell.taskview.TaskView;
52 
53 import com.google.common.annotations.VisibleForTesting;
54 
55 import java.util.Set;
56 
57 /**
58  * Basic Launcher for Android Automotive which demonstrates the use of {@link TaskView} to host
59  * maps content and uses a Model-View-Presenter structure to display content in cards.
60  *
61  * <p>Implementations of the Launcher that use the given layout of the main activity
62  * (car_launcher.xml) can customize the home screen cards by providing their own
63  * {@link HomeCardModule} for R.id.top_card or R.id.bottom_card. Otherwise, implementations that
64  * use their own layout should define their own activity rather than using this one.
65  *
66  * <p>Note: On some devices, the TaskView may render with a width, height, and/or aspect
67  * ratio that does not meet Android compatibility definitions. Developers should work with content
68  * owners to ensure content renders correctly when extending or emulating this class.
69  */
70 public class CarLauncher extends FragmentActivity {
71     public static final String TAG = "CarLauncher";
72     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
73 
74     private ActivityManager mActivityManager;
75     private TaskViewManager mTaskViewManager;
76 
77     private CarTaskView mTaskView;
78     private ControlledRemoteCarTaskView mRemoteCarTaskView;
79     private int mCarLauncherTaskId = INVALID_TASK_ID;
80     private Set<HomeCardModule> mHomeCardModules;
81 
82     /** Set to {@code true} once we've logged that the Activity is fully drawn. */
83     private boolean mIsReadyLogged;
84     private boolean mUseSmallCanvasOptimizedMap;
85     private boolean mUseRemoteCarTaskView;
86     private ViewGroup mMapsCard;
87 
88     private final TaskStackListener mTaskStackListener = new TaskStackListener() {
89         @Override
90         public void onTaskFocusChanged(int taskId, boolean focused) {}
91 
92         @Override
93         public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task,
94                 boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) {
95             if (DEBUG) {
96                 Log.d(TAG, "onActivityRestartAttempt: taskId=" + task.taskId
97                         + ", homeTaskVisible=" + homeTaskVisible + ", wasVisible=" + wasVisible);
98             }
99             if (!mUseSmallCanvasOptimizedMap
100                     && !homeTaskVisible
101                     && getTaskViewTaskId() == task.taskId) {
102                 // The embedded map component received an intent, therefore forcibly bringing the
103                 // launcher to the foreground.
104                 bringToForeground();
105                 return;
106             }
107         }
108     };
109 
110     @VisibleForTesting
setCarUserManager(CarUserManager carUserManager)111     void setCarUserManager(CarUserManager carUserManager) {
112         if (mTaskViewManager == null) {
113             Log.w(TAG, "Task view manager is null, cannot set CarUserManager on taskview "
114                     + "manager");
115             return;
116         }
117         mTaskViewManager.setCarUserManager(carUserManager);
118     }
119 
120     @Override
onCreate(Bundle savedInstanceState)121     protected void onCreate(Bundle savedInstanceState) {
122         super.onCreate(savedInstanceState);
123 
124         if (DEBUG) {
125             Log.d(TAG, "onCreate(" + getUserId() + ") displayId=" + getDisplayId());
126         }
127         // Since MUMD is introduced, CarLauncher can be called in the main display of visible users.
128         // In ideal shape, CarLauncher should handle both driver and passengers together.
129         // But, in the mean time, we have separate launchers for driver and passengers, so
130         // CarLauncher needs to reroute the request to Passenger launcher if it is invoked from
131         // the main display of passengers (not driver).
132         // For MUPAND, PassengerLauncher should be the default launcher.
133         // For non-main displays, ATM will invoke SECONDARY_HOME Intent, so the secondary launcher
134         // should handle them.
135         UserManager um = getSystemService(UserManager.class);
136         boolean isPassengerDisplay = getDisplayId() != Display.DEFAULT_DISPLAY
137                 || um.isVisibleBackgroundUsersOnDefaultDisplaySupported();
138         if (isPassengerDisplay) {
139             String passengerLauncherName = getString(R.string.config_passengerLauncherComponent);
140             Intent passengerHomeIntent;
141             if (!passengerLauncherName.isEmpty()) {
142                 ComponentName component = ComponentName.unflattenFromString(passengerLauncherName);
143                 if (component == null) {
144                     throw new IllegalStateException(
145                             "Invalid passengerLauncher name=" + passengerLauncherName);
146                 }
147                 passengerHomeIntent = new Intent(Intent.ACTION_MAIN)
148                         // passenger launcher should be launched in home task in order to
149                         // fix TaskView layering issue
150                         .addCategory(Intent.CATEGORY_HOME)
151                         .setComponent(component);
152             } else {
153                 // No passenger launcher is specified, then use AppsGrid as a fallback.
154                 passengerHomeIntent = CarLauncherUtils.getAppsGridIntent();
155             }
156             ActivityOptions options = ActivityOptions
157                     // No animation for the trampoline.
158                     .makeCustomAnimation(this, /* enterResId=*/ 0, /* exitResId= */ 0)
159                     .setLaunchDisplayId(getDisplayId());
160             startActivity(passengerHomeIntent, options.toBundle());
161             finish();
162             return;
163         }
164 
165         mUseSmallCanvasOptimizedMap =
166                 CarLauncherUtils.isSmallCanvasOptimizedMapIntentConfigured(this);
167         mUseRemoteCarTaskView = getResources().getBoolean(R.bool.config_useRemoteCarTaskView);
168 
169         mActivityManager = getSystemService(ActivityManager.class);
170         mCarLauncherTaskId = getTaskId();
171         TaskStackChangeListeners.getInstance().registerTaskStackListener(mTaskStackListener);
172 
173         // Setting as trusted overlay to let touches pass through.
174         getWindow().addPrivateFlags(PRIVATE_FLAG_TRUSTED_OVERLAY);
175         // To pass touches to the underneath task.
176         getWindow().addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL);
177 
178         // Don't show the maps panel in multi window mode.
179         // NOTE: CTS tests for split screen are not compatible with activity views on the default
180         // activity of the launcher
181         if (isInMultiWindowMode() || isInPictureInPictureMode()) {
182             setContentView(R.layout.car_launcher_multiwindow);
183         } else {
184             setContentView(R.layout.car_launcher);
185             // We don't want to show Map card unnecessarily for the headless user 0.
186             if (!UserHelperLite.isHeadlessSystemUser(getUserId())) {
187                 mMapsCard = findViewById(R.id.maps_card);
188                 if (mMapsCard != null) {
189                     if (mUseRemoteCarTaskView) {
190                         setupRemoteCarTaskView(mMapsCard);
191                     } else {
192                         setUpTaskView(mMapsCard);
193                     }
194                 }
195             }
196         }
197         initializeCards();
198     }
199 
setupRemoteCarTaskView(ViewGroup parent)200     private void setupRemoteCarTaskView(ViewGroup parent) {
201         Car.createCar(/* context= */ this, /* handler= */ null,
202                 Car.CAR_WAIT_TIMEOUT_WAIT_FOREVER,
203                 (car, ready) -> {
204                     if (!ready) {
205                         Log.w(TAG, "CarService is not ready.");
206                         return;
207                     }
208                     CarActivityManager carAM = (CarActivityManager) car.getCarManager(
209                             Car.CAR_ACTIVITY_SERVICE);
210 
211                     carAM.getCarTaskViewController(
212                             this,
213                             getMainExecutor(),
214                             new CarTaskViewControllerCallback() {
215                                 @Override
216                                 public void onConnected(
217                                         CarTaskViewController carTaskViewController) {
218                                     carTaskViewController.createControlledRemoteCarTaskView(
219                                             new ControlledRemoteCarTaskViewConfig.Builder()
220                                                     .setActivityIntent(getMapsIntent())
221                                                     .setShouldAutoRestartOnTaskRemoval(true)
222                                                     .build(),
223                                             getMainExecutor(),
224                                             new ControlledRemoteCarTaskViewCallback() {
225                                                 @Override
226                                                 public void onTaskViewCreated(
227                                                         ControlledRemoteCarTaskView taskView) {
228                                                     mRemoteCarTaskView = taskView;
229                                                     parent.addView(taskView);
230                                                 }
231 
232                                                 @Override
233                                                 public void onTaskViewInitialized() {
234                                                     maybeLogReady();
235                                                 }
236 
237                                                 @Override
238                                                 public void onTaskViewReleased() {
239                                                     mRemoteCarTaskView = null;
240                                                     parent.removeAllViews();
241                                                 }
242                                             });
243                                 }
244 
245                                 @Override
246                                 public void onDisconnected(
247                                         CarTaskViewController carTaskViewController) {
248                                     Log.d(TAG, "onDisconnected");
249                                     mRemoteCarTaskView = null;
250                                     parent.removeAllViews();
251                                 }
252                             });
253                 });
254     }
255 
setUpTaskView(ViewGroup parent)256     private void setUpTaskView(ViewGroup parent) {
257         Set<String> taskViewPackages = new ArraySet<>(getResources().getStringArray(
258                 R.array.config_taskViewPackages));
259         mTaskViewManager = new TaskViewManager(this, getMainThreadHandler());
260 
261         mTaskViewManager.createControlledCarTaskView(
262                 getMainExecutor(),
263                 ControlledCarTaskViewConfig.builder()
264                         .setActivityIntent(getMapsIntent())
265                         // TODO(b/263876526): Enable auto restart after ensuring no CTS failure.
266                         .setAutoRestartOnCrash(false)
267                         .build(),
268                 new ControlledCarTaskViewCallbacks() {
269                     @Override
270                     public void onTaskViewCreated(CarTaskView taskView) {
271                         parent.addView(taskView);
272                         mTaskView = taskView;
273                     }
274 
275                     @Override
276                     public void onTaskViewReady() {
277                         maybeLogReady();
278                     }
279 
280                     @Override
281                     public Set<String> getDependingPackageNames() {
282                         return taskViewPackages;
283                     }
284                 });
285     }
286 
287     @Override
onStart()288     protected void onStart() {
289         super.onStart();
290         // The TaskViewManager might have been released if the user was switched to some other user
291         // and then switched back to the previous user before the previous user is stopped.
292         // In such a case, the TaskViewManager should be recreated.
293         if (!mUseRemoteCarTaskView && mMapsCard != null && mTaskViewManager.isReleased()) {
294             setUpTaskView(mMapsCard);
295         }
296     }
297 
298     @Override
onResume()299     protected void onResume() {
300         super.onResume();
301         maybeLogReady();
302     }
303 
304     @Override
onDestroy()305     protected void onDestroy() {
306         super.onDestroy();
307         TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mTaskStackListener);
308         release();
309     }
310 
isTaskViewInitialized()311     private boolean isTaskViewInitialized() {
312         return (mTaskView != null && mTaskView.isInitialized())
313                 || (mRemoteCarTaskView != null && mRemoteCarTaskView.isInitialized());
314     }
315 
getTaskViewTaskId()316     private int getTaskViewTaskId() {
317         if (mTaskView != null) {
318             return mTaskView.getTaskId();
319         }
320         if (mRemoteCarTaskView != null) {
321             return mRemoteCarTaskView.getTaskInfo() == null
322                     ? INVALID_TASK_ID : mRemoteCarTaskView.getTaskInfo().taskId;
323         }
324         return INVALID_TASK_ID;
325     }
326 
release()327     private void release() {
328         mTaskView = null;
329         mRemoteCarTaskView = null;
330     }
331 
332     @Override
onConfigurationChanged(Configuration newConfig)333     public void onConfigurationChanged(Configuration newConfig) {
334         super.onConfigurationChanged(newConfig);
335         initializeCards();
336     }
337 
initializeCards()338     private void initializeCards() {
339         if (mHomeCardModules == null) {
340             mHomeCardModules = new ArraySet<>();
341             for (String providerClassName : getResources().getStringArray(
342                     R.array.config_homeCardModuleClasses)) {
343                 try {
344                     long reflectionStartTime = System.currentTimeMillis();
345                     HomeCardModule cardModule = (HomeCardModule) Class.forName(
346                             providerClassName).newInstance();
347                     cardModule.setViewModelProvider(new ViewModelProvider( /* owner= */this));
348                     mHomeCardModules.add(cardModule);
349                     if (DEBUG) {
350                         long reflectionTime = System.currentTimeMillis() - reflectionStartTime;
351                         Log.d(TAG, "Initialization of HomeCardModule class " + providerClassName
352                                 + " took " + reflectionTime + " ms");
353                     }
354                 } catch (IllegalAccessException | InstantiationException |
355                         ClassNotFoundException e) {
356                     Log.w(TAG, "Unable to create HomeCardProvider class " + providerClassName, e);
357                 }
358             }
359         }
360         FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
361         for (HomeCardModule cardModule : mHomeCardModules) {
362             transaction.replace(cardModule.getCardResId(), cardModule.getCardView().getFragment());
363         }
364         transaction.commitNow();
365     }
366 
367     /** Logs that the Activity is ready. Used for startup time diagnostics. */
maybeLogReady()368     private void maybeLogReady() {
369         boolean isResumed = isResumed();
370         boolean taskViewInitialized = isTaskViewInitialized();
371         if (DEBUG) {
372             Log.d(TAG, "maybeLogReady(" + getUserId() + "): mapsReady="
373                     + taskViewInitialized + ", started=" + isResumed + ", alreadyLogged: "
374                     + mIsReadyLogged);
375         }
376         if (taskViewInitialized && isResumed) {
377             // We should report every time - the Android framework will take care of logging just
378             // when it's effectively drawn for the first time, but....
379             reportFullyDrawn();
380             if (!mIsReadyLogged) {
381                 // ... we want to manually check that the Log.i below (which is useful to show
382                 // the user id) is only logged once (otherwise it would be logged every time the
383                 // user taps Home)
384                 Log.i(TAG, "Launcher for user " + getUserId() + " is ready");
385                 mIsReadyLogged = true;
386             }
387         }
388     }
389 
390     /** Brings the Car Launcher to the foreground. */
bringToForeground()391     private void bringToForeground() {
392         if (mCarLauncherTaskId != INVALID_TASK_ID) {
393             mActivityManager.moveTaskToFront(mCarLauncherTaskId,  /* flags= */ 0);
394         }
395     }
396 
getMapsIntent()397     private Intent getMapsIntent() {
398         Intent mapIntent = mUseSmallCanvasOptimizedMap
399                 ? CarLauncherUtils.getSmallCanvasOptimizedMapIntent(this)
400                 : CarLauncherUtils.getMapsIntent(this);
401         // Don't want to show this Activity in Recents.
402         mapIntent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
403         return mapIntent;
404     }
405 }
406