| /* |
| * 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); |
| } |
| } |
| } |