blob: e045a4ed764edb8d62e56a94dcac28cce3782ef1 [file] [log] [blame]
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.car.carlauncher;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
import android.app.ActivityManager;
import android.app.TaskStackListener;
import android.car.user.CarUserManager;
import android.content.Intent;
import android.content.res.Configuration;
import android.os.Bundle;
import android.util.Log;
import android.view.ViewGroup;
import android.view.WindowManager;
import androidx.collection.ArraySet;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentTransaction;
import androidx.lifecycle.ViewModelProvider;
import com.android.car.carlauncher.homescreen.HomeCardModule;
import com.android.car.carlauncher.taskstack.TaskStackChangeListeners;
import com.android.car.internal.common.UserHelperLite;
import com.android.wm.shell.TaskView;
import com.android.wm.shell.common.HandlerExecutor;
import com.google.common.annotations.VisibleForTesting;
import java.util.Set;
/**
* Basic Launcher for Android Automotive which demonstrates the use of {@link TaskView} to host
* maps content and uses a Model-View-Presenter structure to display content in cards.
*
* <p>Implementations of the Launcher that use the given layout of the main activity
* (car_launcher.xml) can customize the home screen cards by providing their own
* {@link HomeCardModule} for R.id.top_card or R.id.bottom_card. Otherwise, implementations that
* use their own layout should define their own activity rather than using this one.
*
* <p>Note: On some devices, the TaskView may render with a width, height, and/or aspect
* ratio that does not meet Android compatibility definitions. Developers should work with content
* owners to ensure content renders correctly when extending or emulating this class.
*/
public class CarLauncher extends FragmentActivity {
public static final String TAG = "CarLauncher";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private ActivityManager mActivityManager;
private TaskViewManager mTaskViewManager;
private CarTaskView mTaskView;
private int mCarLauncherTaskId = INVALID_TASK_ID;
private Set<HomeCardModule> mHomeCardModules;
/** Set to {@code true} once we've logged that the Activity is fully drawn. */
private boolean mIsReadyLogged;
private boolean mUseSmallCanvasOptimizedMap;
private final TaskStackListener mTaskStackListener = new TaskStackListener() {
@Override
public void onTaskFocusChanged(int taskId, boolean focused) {}
@Override
public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task,
boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) {
if (DEBUG) {
Log.d(TAG, "onActivityRestartAttempt: taskId=" + task.taskId
+ ", homeTaskVisible=" + homeTaskVisible + ", wasVisible=" + wasVisible);
}
if (!mUseSmallCanvasOptimizedMap
&& !homeTaskVisible
&& mTaskView != null
&& mTaskView.getTaskId() == task.taskId) {
// The embedded map component received an intent, therefore forcibly bringing the
// launcher to the foreground.
bringToForeground();
return;
}
}
};
@VisibleForTesting
void setCarUserManager(CarUserManager carUserManager) {
if (mTaskViewManager == null) {
Log.w(TAG, "Task view manager is null, cannot set CarUserManager");
return;
}
mTaskViewManager.setCarUserManager(carUserManager);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (CarLauncherUtils.isCustomDisplayPolicyDefined(this)) {
Intent controlBarIntent = new Intent(this, ControlBarActivity.class);
controlBarIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(controlBarIntent);
startActivity(
CarLauncherUtils.getMapsIntent(this).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
// Register health check monitor for maps.
finish();
return;
}
mUseSmallCanvasOptimizedMap =
CarLauncherUtils.isSmallCanvasOptimizedMapIntentConfigured(this);
mActivityManager = getSystemService(ActivityManager.class);
mCarLauncherTaskId = getTaskId();
TaskStackChangeListeners.getInstance().registerTaskStackListener(mTaskStackListener);
// Setting as trusted overlay to let touches pass through.
getWindow().addPrivateFlags(PRIVATE_FLAG_TRUSTED_OVERLAY);
// To pass touches to the underneath task.
getWindow().addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL);
// Don't show the maps panel in multi window mode.
// NOTE: CTS tests for split screen are not compatible with activity views on the default
// activity of the launcher
if (isInMultiWindowMode() || isInPictureInPictureMode()) {
setContentView(R.layout.car_launcher_multiwindow);
} else {
setContentView(R.layout.car_launcher);
// We don't want to show Map card unnecessarily for the headless user 0.
if (!UserHelperLite.isHeadlessSystemUser(getUserId())) {
ViewGroup mapsCard = findViewById(R.id.maps_card);
if (mapsCard != null) {
setUpTaskView(mapsCard);
}
}
}
initializeCards();
}
private void setUpTaskView(ViewGroup parent) {
Set<String> taskViewPackages = new ArraySet<>(getResources().getStringArray(
R.array.config_taskViewPackages));
mTaskViewManager = new TaskViewManager(this,
new HandlerExecutor(getMainThreadHandler()));
Intent mapIntent = mUseSmallCanvasOptimizedMap
? CarLauncherUtils.getSmallCanvasOptimizedMapIntent(this)
: CarLauncherUtils.getMapsIntent(this);
mTaskViewManager.createControlledCarTaskView(
getMainExecutor(),
mapIntent,
/* autoRestartOnCrash = */ false,
new ControlledCarTaskViewCallbacks() {
@Override
public void onTaskViewCreated(CarTaskView taskView) {
parent.addView(taskView);
mTaskView = taskView;
}
@Override
public void onTaskViewReady() {
maybeLogReady();
}
@Override
public Set<String> getDependingPackageNames() {
return taskViewPackages;
}
});
}
@Override
protected void onResume() {
super.onResume();
maybeLogReady();
}
@Override
protected void onDestroy() {
super.onDestroy();
if (CarLauncherUtils.isCustomDisplayPolicyDefined(this)) {
return;
}
TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mTaskStackListener);
release();
}
private void release() {
mTaskView = null;
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
if (CarLauncherUtils.isCustomDisplayPolicyDefined(this)) {
return;
}
initializeCards();
}
private void initializeCards() {
if (mHomeCardModules == null) {
mHomeCardModules = new ArraySet<>();
for (String providerClassName : getResources().getStringArray(
R.array.config_homeCardModuleClasses)) {
try {
long reflectionStartTime = System.currentTimeMillis();
HomeCardModule cardModule = (HomeCardModule) Class.forName(
providerClassName).newInstance();
cardModule.setViewModelProvider(new ViewModelProvider( /* owner= */this));
mHomeCardModules.add(cardModule);
if (DEBUG) {
long reflectionTime = System.currentTimeMillis() - reflectionStartTime;
Log.d(TAG, "Initialization of HomeCardModule class " + providerClassName
+ " took " + reflectionTime + " ms");
}
} catch (IllegalAccessException | InstantiationException |
ClassNotFoundException e) {
Log.w(TAG, "Unable to create HomeCardProvider class " + providerClassName, e);
}
}
}
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
for (HomeCardModule cardModule : mHomeCardModules) {
transaction.replace(cardModule.getCardResId(), cardModule.getCardView());
}
transaction.commitNow();
}
/** Logs that the Activity is ready. Used for startup time diagnostics. */
private void maybeLogReady() {
boolean isResumed = isResumed();
boolean taskViewInitialized = mTaskView != null && mTaskView.isInitialized();
if (DEBUG) {
Log.d(TAG, "maybeLogReady(" + getUserId() + "): mapsReady="
+ taskViewInitialized + ", started=" + isResumed + ", alreadyLogged: "
+ mIsReadyLogged);
}
if (taskViewInitialized && isResumed) {
// We should report every time - the Android framework will take care of logging just
// when it's effectively drawn for the first time, but....
reportFullyDrawn();
if (!mIsReadyLogged) {
// ... we want to manually check that the Log.i below (which is useful to show
// the user id) is only logged once (otherwise it would be logged every time the
// user taps Home)
Log.i(TAG, "Launcher for user " + getUserId() + " is ready");
mIsReadyLogged = true;
}
}
}
/** Brings the Car Launcher to the foreground. */
private void bringToForeground() {
if (mCarLauncherTaskId != INVALID_TASK_ID) {
mActivityManager.moveTaskToFront(mCarLauncherTaskId, /* flags= */ 0);
}
}
}